Commit Graph

7593 Commits

Author SHA1 Message Date
Micha Reiser 84e76f4d04
[ty] Avoid second lookup for `infer_maybe_standalone_expression` (#19439) 2025-07-20 18:22:04 +02:00
UnboundVariable 0acc273286
[ty] Implemented "go to definition" support for import statements (#19428)
This PR extends the "go to declaration" and "go to definition"
functionality to support import statements — both standard imports and
"from" import forms.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-19 11:22:07 -07:00
Micha Reiser 93a9fabb26
[ty] Avoid secondary tree traversal to get call expression for keyword arguments (#19429) 2025-07-19 18:21:27 +02:00
Micha Reiser 98d1811dd1
[ty] Add goto definition to playground (#19425) 2025-07-19 15:44:44 +02:00
Aria Desires 06f9f52e59
[ty] Add support for `@warnings.deprecated` (#19376)
* [x] basic handling
  * [x] parse and discover `@warnings.deprecated` attributes
  * [x] associate them with function definitions
  * [x] associate them with class definitions
  * [x] add a new "deprecated" diagnostic
* [x] ensure diagnostic is styled appropriately for LSPs
(DiagnosticTag::Deprecated)

* [x] functions
  * [x] fire on calls
  * [x] fire on arbitrary references 
* [x] classes
  * [x] fire on initializers
  * [x] fire on arbitrary references
* [x] methods
  * [x] fire on calls
  * [x] fire on arbitrary references
* [ ] overloads
  * [ ] fire on calls
  * [ ] fire on arbitrary references(??? maybe not ???)
  * [ ] only fire if the actual selected overload is deprecated 

* [ ] dunder desugarring (warn on deprecated `__add__` if `+` is
invoked)
* [ ] alias supression? (don't warn on uses of variables that deprecated
items were assigned to)

* [ ] import logic
  * [x] fire on imports of deprecated items
* [ ] suppress subsequent diagnostics if the import diagnostic fired (is
this handled by alias supression?)
  * [x] fire on all qualified references (`module.mydeprecated`)
  * [x] fire on all references that depend on a `*` import
    


Fixes https://github.com/astral-sh/ty/issues/153
2025-07-18 23:50:29 +00:00
Jack O'Connor e9a64e5825
[ty] make `del x` force local resolution of `x` in the current scope (#19389)
Fixes https://github.com/astral-sh/ty/issues/769.

**Updated:** The preferred approach here is to keep the SemanticIndex
simple (`del` of any name marks that name "bound" in the current scope)
and to move complexity to type inference (free variable resolution stops
when it finds a binding, unless that binding is declared `nonlocal`). As
part of this change, free variable resolution will now union the types
it finds as it walks in enclosing scopes. This approach is still
incomplete, because it doesn't consider inner scopes or sibling scopes,
but it improves the common case.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-07-18 14:58:32 -07:00
UnboundVariable 360eb7005f
[ty] Added support for "go to definition" for attribute accesses and keyword arguments (#19417)
This PR builds upon #19371. It addresses a few additional code review
suggestions and adds support for attribute accesses (expressions of the
form `x.y`) and keyword arguments within call expressions.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-18 11:33:57 -07:00
Micha Reiser 630c7a3152
[ty] Reduce number of inline stored definitions per place (#19409) 2025-07-18 18:28:46 +02:00
Ibraheem Ahmed e6e029a8b7
Update salsa (#19258)
## Summary

Pulls in https://github.com/salsa-rs/salsa/pull/934.
2025-07-18 12:14:28 -04:00
Andrew Gallant 64f9481fd0
[ty] Add caching for submodule completion suggestions (#19408)
This change makes it so we aren't doing a directory traversal every time
we ask for completions from a module. Specifically, submodules that
aren't attributes of their parent module can only be discovered by
looking at the directory tree. But we want to avoid doing a directory
scan unless we think there are changes.

To make this work, this change does a little bit of surgery to
`FileRoot`. Previously, a `FileRoot` was only used for library search
paths. Its revision was bumped whenever a file in that tree was added,
deleted or even modified (to support the discovery of `pth` files and
changes to its contents). This generally seems fine since these are
presumably dependency paths that shouldn't change frequently.

In this change, we add a `FileRoot` for the project. But having the
`FileRoot`'s revision bumped for every change in the project makes
caching based on that `FileRoot` rather ineffective. That is, cache
invalidation will occur too aggressively. To the point that there is
little point in adding caching in the first place. To mitigate this, a
`FileRoot`'s revision is only bumped on a change to a child file's
contents when the `FileRoot` is a `LibrarySearchPath`. Otherwise, we
only bump the revision when a file is created or added.

The effect is that, at least in VS Code, when a new module is added or
removed, this change is picked up and the cache is properly invalidated.
Other LSP clients with worse support for file watching (which seems to
be the case for the CoC vim plugin that I use) don't work as well. Here,
the cache is less likely to be invalidated which might cause completions
to have stale results. Unless there's an obvious way to fix or improve
this, I propose punting on improvements here for now.
2025-07-18 11:54:27 -04:00
Dhruv Manilawala 99d0ac60b4
[ty] Track open files in the server (#19264)
## Summary

This PR updates the server to keep track of open files both system and
virtual files.

This is done by updating the project by adding the file in the open file
set in `didOpen` notification and removing it in `didClose`
notification.

This does mean that for workspace diagnostics, ty will only check open
files because the behavior of different diagnostic builder is to first
check `is_file_open` and only add diagnostics for open files. So, this
required updating the `is_file_open` model to be `should_check_file`
model which validates whether the file needs to be checked based on the
`CheckMode`. If the check mode is open files only then it will check
whether the file is open. If it's all files then it'll return `true` by
default.

Closes: astral-sh/ty#619

## Test Plan

### Before

There are two files in the project: `__init__.py` and `diagnostics.py`.

In the video, I'm demonstrating the old behavior where making changes to
the (open) `diagnostics.py` file results in re-parsing the file:


https://github.com/user-attachments/assets/c2ac0ecd-9c77-42af-a924-c3744b146045

### After

Same setup as above.

In the video, I'm demonstrating the new behavior where making changes to
the (open) `diagnostics.py` file doesn't result in re-parting the file:


https://github.com/user-attachments/assets/7b82fe92-f330-44c7-b527-c841c4545f8f
2025-07-18 19:33:35 +05:30
Andrew Gallant ba7ed3a6f9
[ty] Use `…` as the "cut" indicator in diagnostic rendering (#19420)
This makes ty match ruff's behavior. Specifically, we want to use `…`
instead of the default `...` because `...` has special significance in
Python.
2025-07-18 07:46:48 -04:00
justin 39b41838f3
[ty] synthesize __setattr__ for frozen dataclasses (#19307)
## Summary

Synthesize a `__setattr__` method with a return type of `Never` for
frozen dataclasses.

https://docs.python.org/3/library/dataclasses.html#frozen-instances

https://docs.python.org/3/library/dataclasses.html#dataclasses.FrozenInstanceError

### Related
https://github.com/astral-sh/ty/issues/111
https://github.com/astral-sh/ruff/pull/17974#discussion_r2108527106
https://github.com/astral-sh/ruff/pull/18347#discussion_r2128174665

## Test Plan

New Markdown tests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-18 11:35:05 +02:00
UnboundVariable c7640a433e
[ty] Fixed bug in semantic token provider for parameters. (#19418)
This fixes https://github.com/astral-sh/ty/issues/832.

New tests were added to prevent future regressions.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-18 00:02:23 -07:00
Micha Reiser 1765014be3
[ty] Shrink reachability constraints (#19410) 2025-07-18 07:36:18 +02:00
Brent Westbrook 997dc2e7cc
Move JUnit rendering to `ruff_db` (#19370)
Summary
--

This PR moves the JUnit output format to the new rendering
infrastructure. As I
mention in a TODO in the code, there's some code that will be shared
with the
`grouped` output format. Hopefully I'll have that PR up too by the time
this one
is reviewed.

Test Plan
--

Existing tests moved to `ruff_db`

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-17 18:24:13 -04:00
Douglas Creager 4aee0398cb
[ty] Show the raw argument type in `reveal_type` (#19400)
This PR is changes how `reveal_type` determines what type to reveal, in
a way that should be a no-op to most callers.

Previously, we would reveal the type of the first parameter, _after_ all
of the call binding machinery had done its work. This includes inferring
the specialization of a generic function, and then applying that
specialization to all parameter and argument types, which is relevant
since the typeshed definition of `reveal_type` is generic:

```pyi
def reveal_type(obj: _T, /) -> _T: ...
```

Normally this does not matter, since we infer `_T = [arg type]` and
apply that to the parameter type, yielding `[arg type]`. But applying
that specialization also simplifies the argument type, which makes
`reveal_type` less useful as a debugging aid when we want to see the
actual, raw, unsimplified argument type.

With this patch, we now grab the original unmodified argument type and
reveal that instead.

In addition to making the debugging aid example work, this also makes
our `reveal_type` implementation more robust to custom typeshed
definitions, such as

```py
def reveal_type(obj: Any) -> Any: ...
```

(That custom definition is probably not what anyone would want, since
you wouldn't be able to depend on the return type being equivalent to
the argument type, but still)
2025-07-17 16:50:29 -04:00
Brent Westbrook 1fd9103e81
Canonicalize path before filtering (#19407)
## Summary

This came up on
[Discord](https://discord.com/channels/1039017663004942429/1343692072921731082/1395447082520678440)
and also in #19387, but on macOS the tmp directory is a symlink to
`/private/tmp`, which breaks this filter. I'm still not quite sure why
only these tests are affected when we use the `tempdir_filter`
elsewhere, but hopefully this fixes the immediate issue. Just
`tempdir.path().canonicalize()` also worked, but I used `dunce` since
that's what I saw in other tests (I guess it's not _just_ these tests).

Some related links from uv:
-
1b2f212e8b/crates/uv/tests/it/common/mod.rs (L1161-L1178)
-
1b2f212e8b/crates/uv/tests/it/common/mod.rs (L424-L438)
- https://github.com/astral-sh/uv/pull/14290

Thanks to @zanieb for those!

## Test Plan

I tested the `main` branch on my MacBook and reproduced the test
failure, then confirmed that the tests pass after the change. Now to
make sure it passes on Windows, which caused most of the trouble in the
first PR!
2025-07-17 14:02:17 -04:00
Dylan ee2759b365
Bump 0.12.4 (#19406) 2025-07-17 12:14:01 -05:00
Aria Desires 35f33d9bf5
[ty] publish settings diagnostics (#19335) 2025-07-17 11:57:00 -04:00
chiri 5d78b3117a
[`flake8-use-pathlib`] Add autofix for `PTH109` (#19245)
## Summary

Part of #2331

## Test Plan

`cargo nextest run flake8_use_pathlib`
2025-07-17 10:11:43 -04:00
Dhruv Manilawala c2a05b4825
[ty] Use `bitflags` for resolved client capabilities (#19393)
## Summary

This PR updates the `ResolvedClientCapabilities` to be represented as
`bitflags`. This allows us to remove the `Arc` as the type becomes copy.

Additionally, this PR also fixed the goto definition and declaration
code to use the `textDocument.definition.linkSupport` and
`textDocument.declaration.linkSupport` client capability.

This PR also removes the unused client capabilities which are
`code_action_deferred_edit_resolution`, `apply_edit`, and
`document_changes` which are all related to auto-fix ability.
2025-07-17 15:31:47 +05:30
UnboundVariable fae0b5c89e
[ty] Initial implementation of declaration and definition providers. (#19371)
This PR implements "go to definition" and "go to declaration"
functionality for name nodes only. Future PRs will add support for
attributes, module names in import statements, keyword argument names,
etc.

This PR:
* Registers a declaration and definition request handler for the
language server.
* Splits out the `goto_type_definition` into its own module. The `goto`
module contains functionality that is common to `goto_type_definition`,
`goto_declaration` and `goto_definition`.
* Roughs in a new module `stub_mapping` that is not yet implemented. It
will be responsible for mapping a definition in a stub file to its
corresponding definition(s) in an implementation (source) file.
* Adds a new IDE support function `definitions_for_name` that collects
all of the definitions associated with a name and resolves any imports
(recursively) to find the original definitions associated with that
name.
* Adds a new `VisibleAncestorsIter` stuct that iterates up the scope
hierarchy but skips scopes that are not visible to starting scope.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-16 15:07:24 -07:00
Matthew Mckee cbe94b094b
[ty] Support empty function bodies in `if TYPE_CHECKING` blocks (#19372)
## Summary

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

Supports having a blank function body inside `if TYPE_CHECKING` block or
in the elif or else of a `if not TYPE_CHECKING` block.

```py
if TYPE_CHECKING:
    def foo() -> int: ...

if not TYPE_CHECKING: ...
else:     
    def bar() -> int: ...
```

## Test Plan

Update `function/return_type.md`

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-07-16 14:48:04 -06:00
Dan Parizher 029de784f1
[`flake8-use-pathlib`] Fix false negative on direct `Path()` instantiation (`PTH210`) (#19388)
## Summary

Fixes #19329

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-16 20:43:03 +00:00
frank ff94fe7447
Treat form feed as valid whitespace before a semicolon (#19343)
fixes #19310
2025-07-16 16:39:05 -04:00
Dan Parizher b2501b45e0
[`pylint`] Detect indirect `pathlib.Path` usages for `unspecified-encoding` (PLW1514) (#19304)
## Summary

Fixes #19294

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-16 12:57:31 -04:00
Dan Parizher 291699b375
[`refurb`] `FURB164` fix should validate arguments and should usually be marked unsafe (#19136)
## Summary

Fixes #19076

An attempt at fixing #19076 where the rule could change program behavior
by incorrectly converting from_float/from_decimal method calls to
constructor calls.

The fix implements argument validation using Ruff's existing type
inference system (`ResolvedPythonType`, `typing::is_int`,
`typing::is_float`) to determine when conversions are actually safe,
adds logic to detect invalid method calls (wrong argument counts,
incorrect keyword names) and suppress fixes for them, and changes the
default fix applicability from `Safe` to `Unsafe` with safe fixes only
offered when the argument type is known to be compatible and no
problematic keywords are used.

One uncertainty is whether the type inference catches all possible edge
cases in complex codebases, but the new approach is significantly more
conservative and safer than the previous implementation.

## Test Plan

I updated the existing test fixtures with edge cases from the issue and
manually verified behavior with temporary test files for
valid/unsafe/invalid scenarios.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-16 15:38:33 +00:00
Jack O'Connor 64ac7d7dbf
[ty] use the name span rather than the statement span for unresolved `global` lints (#19379)
This is a follow-up to https://github.com/astral-sh/ruff/pull/19344 that
improves the error formatting slightly. For example with this program:

```py
def f():
    global foo, bar
```

Before we printed:

```
1 | def f():
2 |     global foo, bar
  |     ^^^^^^^^^^^^^^^ `foo` has no declarations or bindings in the global scope
...
1 | def f():
2 |     global foo, bar
  |     ^^^^^^^^^^^^^^^ `bar` has no declarations or bindings in the global scope
```

Now we print:

```
1 | def f():
2 |     global foo, bar
  |            ^^^ `foo` has no declarations or bindings in the global scope
...
1 | def f():
2 |     global foo, bar
  |                 ^^^ `bar` has no declarations or bindings in the global scope
```

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-16 15:34:47 +00:00
Jack O'Connor 5f2e855c29 allow reads of "free" variables to refer to a `global` declaration
Previously this worked if there was also a binding in the same scope as
the `global` declaration (probably almost always the case), but CPython
doesn't require this.

This change surfaced an error in an existing test, where a global
variable was only ever declared and bound using the `global` keyword,
and never mentioned explicitly in the global scope. @AlexWaygood
suggested we probably want to keep that requirement, so I'm adding an a
new test for that on top of fixing the failing test.
2025-07-16 08:30:42 -07:00
Jack O'Connor 3b4667ec32 respect annotation-only declarations in `infer_place_load` 2025-07-16 08:30:42 -07:00
Brent Westbrook 893f5727e5
[`flake8-type-checking`, `pyupgrade`, `ruff`] Add `from __future__ import annotations` when it would allow new fixes (`TC001`, `TC002`, `TC003`, `UP037`, `RUF013`) (#19100)
## Summary

This is a second attempt at addressing
https://github.com/astral-sh/ruff/issues/18502 instead of reusing
`FA100` (#18919).

This PR:
- adds a new `lint.allow-importing-future-annotations` option
- uses the option to add a `__future__` import when it would trigger
`TC001`, `TC002`, or `TC003`
- uses the option to add an import when it would allow unquoting more
annotations in [quoted-annotation
(UP037)](https://docs.astral.sh/ruff/rules/quoted-annotation/#quoted-annotation-up037)
- uses the option to allow the `|` union syntax before 3.10 in
[implicit-optional
(RUF013)](https://docs.astral.sh/ruff/rules/implicit-optional/#implicit-optional-ruf013)

I started adding a fix for [runtime-string-union
(TC010)](https://docs.astral.sh/ruff/rules/runtime-string-union/#runtime-string-union-tc010)
too, as mentioned in my previous
[comment](https://github.com/astral-sh/ruff/issues/18502#issuecomment-3005238092),
but some of the existing tests already imported `from __future__ import
annotations`, so I think we intentionally flag these cases for the user
to inspect. Adding the import is _a_ fix but probably not the best one.

## Test Plan

Existing `TC` tests, new copies of them with the option enabled, and new
tests based on ideas in
https://github.com/astral-sh/ruff/pull/18919#discussion_r2166292705 and
the following thread. For UP037 and RUF013, the new tests are also
copies of the existing tests, with the new option enabled. The easiest
way to review them is probably by their diffs from the existing
snapshots:

### UP037

`UP037_0.py` and `UP037_2.pyi` have no diffs. The diff for `UP037_1.py`
is below. It correctly unquotes an annotation in module scope that would
otherwise be invalid.

<details><summary>UP037_1.py</summary>

```diff
3d2
< snapshot_kind: text
23c22,42
< 12 12 |
---
> 12 12 |
>
> UP037_1.py:14:4: UP037 [*] Remove quotes from type annotation
>    |
> 13 | # OK
> 14 | X: "Tuple[int, int]" = (0, 0)
>    |    ^^^^^^^^^^^^^^^^^ UP037
>    |
>    = help: Remove quotes
>
> ℹ Unsafe fix
>    1  |+from __future__ import annotations
> 1  2  | from typing import TYPE_CHECKING
> 2  3  |
> 3  4  | if TYPE_CHECKING:
> --------------------------------------------------------------------------------
> 11 12 |
> 12 13 |
> 13 14 | # OK
> 14    |-X: "Tuple[int, int]" = (0, 0)
>    15 |+X: Tuple[int, int] = (0, 0)
```

</details>

### RUF013

The diffs here are mostly just the imports because the original snaps
were on 3.13. So we're getting the same fixes now on 3.9.

<details><summary>RUF013_0.py</summary>

```diff
3d2
< snapshot_kind: text
14,16c13,20
< 17 17 |     pass
< 18 18 | 
< 19 19 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 17 18 |     pass
> 18 19 | 
> 19 20 | 
18,21c22,25
<    20 |+def f(arg: int | None = None):  # RUF013
< 21 21 |     pass
< 22 22 | 
< 23 23 | 
---
>    21 |+def f(arg: int | None = None):  # RUF013
> 21 22 |     pass
> 22 23 | 
> 23 24 | 
32,34c36,43
< 21 21 |     pass
< 22 22 | 
< 23 23 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 21 22 |     pass
> 22 23 | 
> 23 24 | 
36,39c45,48
<    24 |+def f(arg: str | None = None):  # RUF013
< 25 25 |     pass
< 26 26 | 
< 27 27 | 
---
>    25 |+def f(arg: str | None = None):  # RUF013
> 25 26 |     pass
> 26 27 | 
> 27 28 | 
50,52c59,66
< 25 25 |     pass
< 26 26 | 
< 27 27 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 25 26 |     pass
> 26 27 | 
> 27 28 | 
54,57c68,71
<    28 |+def f(arg: Tuple[str] | None = None):  # RUF013
< 29 29 |     pass
< 30 30 | 
< 31 31 | 
---
>    29 |+def f(arg: Tuple[str] | None = None):  # RUF013
> 29 30 |     pass
> 30 31 | 
> 31 32 | 
68,70c82,89
< 55 55 |     pass
< 56 56 | 
< 57 57 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 55 56 |     pass
> 56 57 | 
> 57 58 | 
72,75c91,94
<    58 |+def f(arg: Union | None = None):  # RUF013
< 59 59 |     pass
< 60 60 | 
< 61 61 | 
---
>    59 |+def f(arg: Union | None = None):  # RUF013
> 59 60 |     pass
> 60 61 | 
> 61 62 | 
86,88c105,112
< 59 59 |     pass
< 60 60 | 
< 61 61 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 59 60 |     pass
> 60 61 | 
> 61 62 | 
90,93c114,117
<    62 |+def f(arg: Union[int] | None = None):  # RUF013
< 63 63 |     pass
< 64 64 | 
< 65 65 | 
---
>    63 |+def f(arg: Union[int] | None = None):  # RUF013
> 63 64 |     pass
> 64 65 | 
> 65 66 | 
104,106c128,135
< 63 63 |     pass
< 64 64 | 
< 65 65 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 63 64 |     pass
> 64 65 | 
> 65 66 | 
108,111c137,140
<    66 |+def f(arg: Union[int, str] | None = None):  # RUF013
< 67 67 |     pass
< 68 68 | 
< 69 69 | 
---
>    67 |+def f(arg: Union[int, str] | None = None):  # RUF013
> 67 68 |     pass
> 68 69 | 
> 69 70 | 
122,124c151,158
< 82 82 |     pass
< 83 83 | 
< 84 84 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 82 83 |     pass
> 83 84 | 
> 84 85 | 
126,129c160,163
<    85 |+def f(arg: int | float | None = None):  # RUF013
< 86 86 |     pass
< 87 87 | 
< 88 88 | 
---
>    86 |+def f(arg: int | float | None = None):  # RUF013
> 86 87 |     pass
> 87 88 | 
> 88 89 | 
140,142c174,181
< 86 86 |     pass
< 87 87 | 
< 88 88 | 
---
>    1  |+from __future__ import annotations
> 1  2  | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 86 87 |     pass
> 87 88 | 
> 88 89 | 
144,147c183,186
<    89 |+def f(arg: int | float | str | bytes | None = None):  # RUF013
< 90 90 |     pass
< 91 91 | 
< 92 92 | 
---
>    90 |+def f(arg: int | float | str | bytes | None = None):  # RUF013
> 90 91 |     pass
> 91 92 | 
> 92 93 | 
158,160c197,204
< 105 105 |     pass
< 106 106 | 
< 107 107 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 105 106 |     pass
> 106 107 | 
> 107 108 | 
162,165c206,209
<     108 |+def f(arg: Literal[1] | None = None):  # RUF013
< 109 109 |     pass
< 110 110 | 
< 111 111 | 
---
>     109 |+def f(arg: Literal[1] | None = None):  # RUF013
> 109 110 |     pass
> 110 111 | 
> 111 112 | 
176,178c220,227
< 109 109 |     pass
< 110 110 | 
< 111 111 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 109 110 |     pass
> 110 111 | 
> 111 112 | 
180,183c229,232
<     112 |+def f(arg: Literal[1, "foo"] | None = None):  # RUF013
< 113 113 |     pass
< 114 114 | 
< 115 115 | 
---
>     113 |+def f(arg: Literal[1, "foo"] | None = None):  # RUF013
> 113 114 |     pass
> 114 115 | 
> 115 116 | 
194,196c243,250
< 128 128 |     pass
< 129 129 | 
< 130 130 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 128 129 |     pass
> 129 130 | 
> 130 131 | 
198,201c252,255
<     131 |+def f(arg: Annotated[int | None, ...] = None):  # RUF013
< 132 132 |     pass
< 133 133 | 
< 134 134 | 
---
>     132 |+def f(arg: Annotated[int | None, ...] = None):  # RUF013
> 132 133 |     pass
> 133 134 | 
> 134 135 | 
212,214c266,273
< 132 132 |     pass
< 133 133 | 
< 134 134 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 132 133 |     pass
> 133 134 | 
> 134 135 | 
216,219c275,278
<     135 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None):  # RUF013
< 136 136 |     pass
< 137 137 | 
< 138 138 | 
---
>     136 |+def f(arg: Annotated[Annotated[int | str | None, ...], ...] = None):  # RUF013
> 136 137 |     pass
> 137 138 | 
> 138 139 | 
232,234c291,298
< 148 148 | 
< 149 149 | 
< 150 150 | def f(
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 148 149 | 
> 149 150 | 
> 150 151 | def f(
236,239c300,303
<     151 |+    arg1: int | None = None,  # RUF013
< 152 152 |     arg2: Union[int, float] = None,  # RUF013
< 153 153 |     arg3: Literal[1, 2, 3] = None,  # RUF013
< 154 154 | ):
---
>     152 |+    arg1: int | None = None,  # RUF013
> 152 153 |     arg2: Union[int, float] = None,  # RUF013
> 153 154 |     arg3: Literal[1, 2, 3] = None,  # RUF013
> 154 155 | ):
253,255c317,324
< 149 149 | 
< 150 150 | def f(
< 151 151 |     arg1: int = None,  # RUF013
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 149 150 | 
> 150 151 | def f(
> 151 152 |     arg1: int = None,  # RUF013
257,260c326,329
<     152 |+    arg2: Union[int, float] | None = None,  # RUF013
< 153 153 |     arg3: Literal[1, 2, 3] = None,  # RUF013
< 154 154 | ):
< 155 155 |     pass
---
>     153 |+    arg2: Union[int, float] | None = None,  # RUF013
> 153 154 |     arg3: Literal[1, 2, 3] = None,  # RUF013
> 154 155 | ):
> 155 156 |     pass
274,276c343,350
< 150 150 | def f(
< 151 151 |     arg1: int = None,  # RUF013
< 152 152 |     arg2: Union[int, float] = None,  # RUF013
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 150 151 | def f(
> 151 152 |     arg1: int = None,  # RUF013
> 152 153 |     arg2: Union[int, float] = None,  # RUF013
278,281c352,355
<     153 |+    arg3: Literal[1, 2, 3] | None = None,  # RUF013
< 154 154 | ):
< 155 155 |     pass
< 156 156 | 
---
>     154 |+    arg3: Literal[1, 2, 3] | None = None,  # RUF013
> 154 155 | ):
> 155 156 |     pass
> 156 157 | 
292,294c366,373
< 178 178 |     pass
< 179 179 | 
< 180 180 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 178 179 |     pass
> 179 180 | 
> 180 181 | 
296,299c375,378
<     181 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None):  # RUF013
< 182 182 |     pass
< 183 183 | 
< 184 184 | 
---
>     182 |+def f(arg: Union[Annotated[int, ...], Union[str, bytes]] | None = None):  # RUF013
> 182 183 |     pass
> 183 184 | 
> 184 185 | 
307c386
<     = help: Convert to `T | None`
---
>     = help: Convert to `Optional[T]`
314c393
<     188 |+def f(arg: "int | None" = None):  # RUF013
---
>     188 |+def f(arg: "Optional[int]" = None):  # RUF013
325c404
<     = help: Convert to `T | None`
---
>     = help: Convert to `Optional[T]`
332c411
<     192 |+def f(arg: "str | None" = None):  # RUF013
---
>     192 |+def f(arg: "Optional[str]" = None):  # RUF013
343c422
<     = help: Convert to `T | None`
---
>     = help: Convert to `Optional[T]`
354,356c433,440
< 201 201 |     pass
< 202 202 | 
< 203 203 | 
---
>     1   |+from __future__ import annotations
> 1   2   | from typing import Annotated, Any, Literal, Optional, Tuple, Union, Hashable
> 2   3   | 
> 3   4   | 
> --------------------------------------------------------------------------------
> 201 202 |     pass
> 202 203 | 
> 203 204 | 
358,361c442,445
<     204 |+def f(arg: Union["int", "str"] | None = None):  # RUF013
< 205 205 |     pass
< 206 206 | 
< 207 207 |
---
>     205 |+def f(arg: Union["int", "str"] | None = None):  # RUF013
> 205 206 |     pass
> 206 207 | 
> 207 208 |
```

</details>

<details><summary>RUF013_1.py</summary>

```diff
3d2
< snapshot_kind: text
15,16c14,16
< 2 2 |
< 3 3 |
---
>   2 |+from __future__ import annotations
> 2 3 |
> 3 4 |
18,19c18,19
<   4 |+def f(arg: int | None = None):  # RUF013
< 5 5 |     pass
---
>   5 |+def f(arg: int | None = None):  # RUF013
> 5 6 |     pass
```

</details>

<details><summary>RUF013_3.py</summary>

```diff
3d2
< snapshot_kind: text
14,16c13,16
< 1 1 | import typing
< 2 2 | 
< 3 3 | 
---
>   1 |+from __future__ import annotations
> 1 2 | import typing
> 2 3 | 
> 3 4 | 
18,21c18,21
<   4 |+def f(arg: typing.List[str] | None = None):  # RUF013
< 5 5 |     pass
< 6 6 | 
< 7 7 | 
---
>   5 |+def f(arg: typing.List[str] | None = None):  # RUF013
> 5 6 |     pass
> 6 7 | 
> 7 8 | 
32,34c32,39
< 19 19 |     pass
< 20 20 | 
< 21 21 | 
---
>    1  |+from __future__ import annotations
> 1  2  | import typing
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 19 20 |     pass
> 20 21 | 
> 21 22 | 
36,39c41,44
<    22 |+def f(arg: typing.Union[int, str] | None = None):  # RUF013
< 23 23 |     pass
< 24 24 | 
< 25 25 | 
---
>    23 |+def f(arg: typing.Union[int, str] | None = None):  # RUF013
> 23 24 |     pass
> 24 25 | 
> 25 26 | 
50,52c55,62
< 26 26 | # Literal
< 27 27 | 
< 28 28 | 
---
>    1  |+from __future__ import annotations
> 1  2  | import typing
> 2  3  | 
> 3  4  | 
> --------------------------------------------------------------------------------
> 26 27 | # Literal
> 27 28 | 
> 28 29 | 
54,55c64,65
<    29 |+def f(arg: typing.Literal[1, "foo", True] | None = None):  # RUF013
< 30 30 |     pass
---
>    30 |+def f(arg: typing.Literal[1, "foo", True] | None = None):  # RUF013
> 30 31 |     pass
```

</details>

<details><summary>RUF013_4.py</summary>

```diff
3d2
< snapshot_kind: text
13,15c12,20
< 12 12 | def multiple_1(arg1: Optional, arg2: Optional = None): ...
< 13 13 |
< 14 14 |
---
> 1  1  | # https://github.com/astral-sh/ruff/issues/13833
>    2  |+from __future__ import annotations
> 2  3  |
> 3  4  | from typing import Optional
> 4  5  |
> --------------------------------------------------------------------------------
> 12 13 | def multiple_1(arg1: Optional, arg2: Optional = None): ...
> 13 14 |
> 14 15 |
17,20c22,25
<    15 |+def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int | None = None): ...
< 16 16 |
< 17 17 |
< 18 18 | def return_type(arg: Optional = None) -> Optional: ...
---
>    16 |+def multiple_2(arg1: Optional, arg2: Optional = None, arg3: int | None = None): ...
> 16 17 |
> 17 18 |
> 18 19 | def return_type(arg: Optional = None) -> Optional: ...
```

</details>

## Future work

This PR does not touch UP006, UP007, or UP045, which are currently
coupled to FA100. If this new approach turns out well, we may eventually
want to deprecate FA100 and add a `__future__` import in those rules'
fixes too.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-16 08:50:52 -04:00
Jack O'Connor e73a8ba571 lint on the `global` keyword if there's no explicit definition in the global scope 2025-07-15 16:56:54 -07:00
David Peter a1edb69ea5
[ty] Enum literal types (#19328)
## Summary

Add a new `Type::EnumLiteral(…)` variant and infer this type for member
accesses on enums.

**Example**: No more `@Todo` types here:
```py
from enum import Enum

class Answer(Enum):
    YES = 1
    NO = 2

    def is_yes(self) -> bool:
        return self == Answer.YES

reveal_type(Answer.YES)  # revealed: Literal[Answer.YES]
reveal_type(Answer.YES == Answer.NO)  # revealed: Literal[False]
reveal_type(Answer.YES.is_yes())  # revealed: bool
```

## Test Plan

* Many new Markdown tests for the new type variant
* Added enum literal types to property tests, ran property tests

## Ecosystem analysis

Summary:

Lots of false positives removed. All of the new diagnostics are
either new true positives (the majority) or known problems. Click for
detailed analysis</summary>

Details:

```diff
AutoSplit (https://github.com/Toufool/AutoSplit)
+ error[call-non-callable] src/capture_method/__init__.py:137:9: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
+ error[call-non-callable] src/capture_method/__init__.py:147:9: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
+ error[call-non-callable] src/capture_method/__init__.py:148:1: Method `__getitem__` of type `bound method CaptureMethodDict.__getitem__(key: Never, /) -> type[CaptureMethodBase]` is not callable on object of type `CaptureMethodDict`
```

New true positives. That `__getitem__` method is apparently annotated
with `Never` to prevent developers from using it.


```diff
dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ error[invalid-assignment] ddtrace/vendor/psutil/_common.py:29:5: Object of type `None` is not assignable to `Literal[AddressFamily.AF_INET6]`
+ error[invalid-assignment] ddtrace/vendor/psutil/_common.py:33:5: Object of type `None` is not assignable to `Literal[AddressFamily.AF_UNIX]`
```

Arguably true positives:
e0a772c28b/ddtrace/vendor/psutil/_common.py (L29)

```diff
ignite (https://github.com/pytorch/ignite)
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:190:34: Argument to bound method `__call__` is incorrect: Expected `((...) -> Unknown) | None`, found `Literal["123"]`
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:220:37: Argument to function `default_event_filter` is incorrect: Expected `Engine`, found `None`
+ error[invalid-argument-type] tests/ignite/engine/test_custom_events.py:220:43: Argument to function `default_event_filter` is incorrect: Expected `int`, found `None`
+ error[call-non-callable] tests/ignite/engine/test_custom_events.py:561:9: Object of type `CustomEvents` is not callable
+ error[invalid-argument-type] tests/ignite/metrics/test_frequency.py:50:38: Argument to bound method `attach` is incorrect: Expected `Events`, found `CallableEventWithFilter`
```

All true positives. Some of them are inside `pytest.raises(TypeError,
…)` blocks 🙃

```diff
meson (https://github.com/mesonbuild/meson)
+ error[invalid-argument-type] unittests/internaltests.py:243:51: Argument to bound method `__init__` is incorrect: Expected `bool`, found `Literal[MachineChoice.HOST]`
+ error[invalid-argument-type] unittests/internaltests.py:271:51: Argument to bound method `__init__` is incorrect: Expected `bool`, found `Literal[MachineChoice.HOST]`
```

New true positives. Enum literals can not be assigned to `bool`, even if
their value types are `0` and `1`.

```diff
poetry (https://github.com/python-poetry/poetry)
+ error[invalid-assignment] src/poetry/console/exceptions.py:101:5: Object of type `Literal[""]` is not assignable to `InitVar[str]`
```

New false positive, missing support for `InitVar`.

```diff
prefect (https://github.com/PrefectHQ/prefect)
+ error[invalid-argument-type] src/integrations/prefect-dask/tests/test_task_runners.py:193:17: Argument is incorrect: Expected `StateType`, found `Literal[StateType.COMPLETED]`
```

This is confusing. There are two definitions
([one](74d8cd93ee/src/prefect/client/schemas/objects.py (L89-L100)),
[two](https://github.com/PrefectHQ/prefect/blob/main/src/prefect/server/schemas/states.py#L40))
of the `StateType` enum. Here, we're trying to assign one to the other.
I don't think that should be allowed, so this is a true positive (?).

```diff
python-htmlgen (https://github.com/srittau/python-htmlgen)
+ error[invalid-assignment] test_htmlgen/form.py:51:9: Object of type `str` is not assignable to attribute `autocomplete` of type `Autocomplete | None`
+ error[invalid-assignment] test_htmlgen/video.py:38:9: Object of type `str` is not assignable to attribute `preload` of type `Preload | None`
```

True positives. [The stubs are
wrong](01e3b911ac/htmlgen/form.pyi (L8-L10)).
These should not contain type annotations, but rather just `OFF = ...`.

```diff
rotki (https://github.com/rotki/rotki)
+ error[invalid-argument-type] rotkehlchen/tests/unit/test_serialization.py:62:30: Argument to bound method `deserialize` is incorrect: Expected `str`, found `Literal[15]`
```

New true positive.

```diff
vision (https://github.com/pytorch/vision)
+ error[unresolved-attribute] test/test_extended_models.py:302:17: Type `type[WeightsEnum]` has no attribute `DEFAULT`
+ error[unresolved-attribute] test/test_extended_models.py:302:58: Type `type[WeightsEnum]` has no attribute `DEFAULT`
```

Also new true positives. No `DEFAULT` member exists on `WeightsEnum`.
2025-07-15 21:31:53 +02:00
github-actions[bot] a0d4e1f854
[ty] Sync vendored typeshed stubs (#19368)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-15 18:14:46 +00:00
Zanie Blue 78dfc8af0f
[ty] Allow `-qq` for silent output mode (#19366)
This matches uv's behavior.

Briefly discussed at
https://github.com/astral-sh/ruff/pull/19233#discussion_r2197930360

I think the most useful case is to avoid piping to `/dev/null` which
hard to do properly in a cross-platform script.
2025-07-15 17:08:19 +00:00
Zanie Blue 0c84652cc5
[ty] Allow `-q` short alias for `--quiet` (#19364) 2025-07-15 12:00:07 -05:00
Jack O'Connor a357a68fc9 distinguish references from definitions in `infer_nonlocal`
The initial implementation of `infer_nonlocal` landed in
https://github.com/astral-sh/ruff/pull/19112 fails to report an error
for this example:

```py
x = 1
def f():
    # This is only a usage of `x`, not a definition. It shouldn't be
    # enough to make the `nonlocal` statement below allowed.
    print(x)
    def g():
        nonlocal x
```

Fix this by continuing to walk enclosing scopes when the place we've
found isn't bound, declared, or `nonlocal`.
2025-07-15 07:55:40 -07:00
Dylan 00e7d1ffd6
[`pycodestyle`] Handle brace escapes for t-strings in logical lines (#19358)
Tracks both f and t-strings in the logical line rules for `pycodestyle`.

Progress towards #15506
2025-07-15 14:48:48 +00:00
Douglas Creager f4d0273532
[ty] Combine CallArguments and CallArgumentTypes (#19337)
We previously had separate `CallArguments` and `CallArgumentTypes` types
in support of our two-phase call binding logic. `CallArguments` would
store only the arity/kind of each argument (positional, keyword,
variadic, etc). We then performed parameter matching using only this
arity/kind information, and then infered the type of each argument,
placing the result of this second phase into a new `CallArgumentTypes`.

In #18996, we will need to infer the types of splatted arguments
_before_ performing parameter matching, since we need to know the
argument type to accurately infer its length, which informs how many
parameters the splatted argument is matched against.

That makes this separation of Rust types no longer useful. This PR
merges everything back into a single `CallArguments`. In the case where
we are performing two-phase call binding, the types will be initialized
to `None`, and updated to the actual argument type during the second
`check_types` phase.

_[This is a refactoring in support of fixing the merge conflicts on
#18996. I've pulled this out into a separate PR to make it easier to
review in isolation.]_
2025-07-15 10:20:58 -04:00
Brent Westbrook e9cac3684a
Move Pylint rendering to `ruff_db` (#19340)
Summary
--

This is a very simple output format, the only decision is what to do if
the file
is missing from the diagnostic. For now, I opted to `unwrap_or_default`
both the
path and the `OneIndexed` row number, giving `:1: main diagnostic
message` in
the test without a file.

Another quirk here is that the path is relativized. I just pasted in the
`relativize_path` and `get_cwd` implementations from `ruff_linter::fs`
for now,
but maybe there's a better place for them.

I didn't see any details about why this needs to be relativized in the
original
[issue](https://github.com/astral-sh/ruff/issues/1953),
[PR](https://github.com/astral-sh/ruff/pull/1995), or in the pylint

[docs](https://flake8.pycqa.org/en/latest/internal/formatters.html#pylint-formatter),
but it did change the results of the CLI integration test when I tried
deleting
it. I haven't been able to reproduce that in the CLI, though, so it may
only
happen with `Command::current_dir`.

Test Plan
--

Tests ported from `ruff_linter` and a new test for the case with no file

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-15 10:14:49 -04:00
Dylan 92a302e291
[`pylint`] Extend invalid string character rules to include t-strings (#19355)
Handle t-strings in PLE2510-15

Progress towards #15506
2025-07-15 07:59:51 -05:00
Alex Waygood 7b8161e80d
Make TC010 docs example more realistic (#19356) 2025-07-15 13:52:21 +01:00
Brent Westbrook e9b0c33703
Move RDJSON rendering to `ruff_db` (#19293)
## Summary

Another output format like #19133. This is the
[reviewdog](https://github.com/reviewdog/reviewdog) output format, which
is somewhat similar to regular JSON. Like #19270, in the first commit I
converted from using `json!` to `Serialize` structs, then in the second
commit I moved the module to `ruff_db`.

The reviewdog
[schema](320a8e73a9/proto/rdf/jsonschema/DiagnosticResult.json)
seems a bit more flexible than our JSON schema, so I'm not sure if we
need any preview checks here. I'll flag the places I wasn't sure about
as review comments.

## Test Plan

New tests in `rdjson.rs`, ported from the old `rjdson.rs` module, as
well as the new CLI output tests.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-15 12:39:21 +00:00
Dylan 82391b5675
[`flake8-use-pathlib`] Skip single dots for `invalid-pathlib-with-suffix` (`PTH210`) on versions >= 3.14 (#19331)
Skips [invalid-pathlib-with-suffix
(PTH210)](https://docs.astral.sh/ruff/rules/invalid-pathlib-with-suffix/#invalid-pathlib-with-suffix-pth210)
for `.with_suffix(".")` on Python versions 3.14 and greater, as per [the
docs](https://docs.python.org/3.14/library/pathlib.html#pathlib.PurePath.with_suffix).

Progress towards #15506
2025-07-15 07:05:00 -05:00
Dylan 464144f1c6
[`ruff`] Allow `strict` kwarg when checking for `starmap-zip` (`RUF058`) in Python 3.14+ (#19333)
In Python 3.14 the keyword-argument `strict` was [added to
`map`](https://docs.python.org/3.14/library/functions.html#map). This PR
adds support for this when replacing a starmap-zip call with map in
[starmap-zip
(RUF058)](https://docs.astral.sh/ruff/rules/starmap-zip/#starmap-zip-ruf058).

Progress towards #15506
2025-07-15 07:04:23 -05:00
Alex Waygood 002f9057db
[ty] Reduce false positives for `TypedDict` types (#19354) 2025-07-15 12:47:19 +01:00
Dhruv Manilawala f3a27406c9
[ty] Remove `ConnectionInitializer` (#19353)
## Summary

This PR removes the `ConnectionInitializer` and inlines the
`initialize_start` and `initialize_finish` calls.

The main benefit of this is that it will allow us to use
[`Connection::memory`](https://docs.rs/lsp-server/latest/lsp_server/struct.Connection.html#method.memory)
in the mock server. That method returns two `Connection` where one of
them will represent the client side connection and the other will be
sent to the `Server::new` call to be used by the server. This way the
mock client can send notifications and requests to mimic the editor.

## Test Plan

I tested out the initialization process and checked that the initialized
result contains the server capabilities and server info.
2025-07-15 17:02:44 +05:30
Alex Waygood 2c9da80985
[ty] Use `Type::string_literal()` more (#19352) 2025-07-15 11:09:07 +00:00
Micha Reiser e506296cec
[ty] Make use of salsa `Lookup` when interning values (#19347) 2025-07-15 09:54:43 +02:00
github-actions[bot] 966cc9d6e9
[ty] Sync vendored typeshed stubs (#19345)
Close and reopen this PR to trigger CI

Co-authored-by: typeshedbot <>
2025-07-15 11:46:59 +05:30
GiGaGon 7b27fe966e
[`pylint`] Make example error out-of-the-box (`PLE2502`) (#19272)
<!--
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
Fixes #14346

This PR makes [bidirectional-unicode
(PLE2502)](https://docs.astral.sh/ruff/rules/bidirectional-unicode/#bidirectional-unicode-ple2502)'s
example error out-of-the-box, by converting it to use one of the test
cases. The documentation in general is also updated to replace
"bidirectional unicode character" with "bidirectional formatting
character", as those are the only ones checked for, and the "unicode"
suffix is redundant. The new example section looks like this:
<img width="1074" height="264" alt="image"
src="https://github.com/user-attachments/assets/cc1d2cb4-b590-4f20-a4d2-15b744872cdd"
/>

The "References" section link is also updated to reflect the rule's
actual behavior.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-14 14:46:23 -04:00
GiGaGon 966fd6f57a
[`pydoclint`] Fix `SyntaxError` from fixes with line continuations (`D201`, `D202`) (#19246)
<!--
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 #7172 by suppressing the fixes for
[docstring-missing-returns
(DOC201)](https://docs.astral.sh/ruff/rules/docstring-missing-returns/#docstring-missing-returns-doc201)
/ [docstring-extraneous-returns
(DOC202)](https://docs.astral.sh/ruff/rules/docstring-extraneous-returns/#docstring-extraneous-returns-doc202)
if there is a surrounding line continuation character `\` that would
make the fix cause a syntax error.

To do this, the lints are changed from `AlwaysFixableViolation` to
`Violation` with `FixAvailability::Sometimes`.

In the case of `DOC201`, the fix is not given if the non-break line ends
in a line continuation character `\`. Note that lines are iterated in
reverse from the docstring to the function definition.

In the case of `DOC202`, the fix is not given if the docstring ends with
a line continuation character `\`.

## Test Plan

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

Added a test case.
2025-07-14 13:31:36 -04:00
github-actions[bot] 4f60f0e925
[ty] Sync vendored typeshed stubs (#19334)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-14 17:34:09 +01:00
GiGaGon 059e90a98f
[`refurb`] Make example error out-of-the-box (`FURB122`) (#19297)
## Summary

Part of #18972

This PR makes [for-loop-writes
(FURB122)](https://docs.astral.sh/ruff/rules/for-loop-writes/#for-loop-writes-furb122)'s
example error out-of-the-box. I also had to re-name the second case's
variables to get both to raise at the same time, I suspect because of
limitations in ruff's current semantic model. New names subject to
bikeshedding, I just went with the least effort `_b` for binary suffix.

[Old example](https://play.ruff.rs/19e8e47a-8058-4013-aef5-e9b5eab65962)
```py
with Path("file").open("w") as f:
    for line in lines:
        f.write(line)

with Path("file").open("wb") as f:
    for line in lines:
        f.write(line.encode())
```

[New example](https://play.ruff.rs/e96b00e5-3c63-47c3-996d-dace420dd711)
```py
from pathlib import Path

with Path("file").open("w") as f:
    for line in lines:
        f.write(line)

with Path("file").open("wb") as f_b:
    for line_b in lines_b:
        f_b.write(line_b.encode())
```

The "Use instead" section was also modified similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-14 11:24:16 -05:00
Juriah a4562ac673
[refurb] Make example error out-of-the-box (FURB177) (#19309)
<!--
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 #18972
This PR makes
[implicit-cwd(FURB177)](https://docs.astral.sh/ruff/rules/implicit-cwd/)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/a0bef229-9626-426f-867f-55cb95ee64d8)
```python
cwd = Path().resolve()
```
[New example](https://play.ruff.rs/bdbea4af-e276-4603-a1b6-88757dfaa399)
```python
from pathlib import Path

cwd = Path().resolve()
```
<!-- What's the purpose of the change? What does it do, and why? -->


## Test Plan

<!-- How was it tested? -->
N/A, no functionality/tests affected
2025-07-14 11:23:02 -05:00
Dhruv Manilawala b4c42eb83b
[ty] Add virtual files to the only project database (#19322)
## Summary

Previously, the virtual files were being added to the default database
that's present on the session. This is wrong because the default
database is for any files that don't belong to any project i.e., they're
outside of any projects managed by the server. Virtual files are neither
part of the project nor it is outside the projects. This was not the
intention as in the initial version, virtual files were being added to
the only project database managed by the server.

This PR fixes this by reverting back to the original behavior where
virtual files will be added to the only project database present. When
support for multiple workspace and project is added, this will require
updating (https://github.com/astral-sh/ty/issues/794).

This is required for #19264 because workspace diagnostics doesn't check
the default project database yet. Ideally, the default db should be
checked as well.

The implementation of this PR means that virtual files are now being
included for workspace diagnostics but it doesn't work completely e.g.,
if I save an untitled file the diagnostics disappears but it doesn't
appear back for the (now) saved file on disk as shown in the following
video demonstration:



https://github.com/user-attachments/assets/123e8d20-1e95-4c7d-b7eb-eb65be8c476e
2025-07-14 20:17:51 +05:30
Dylan 2a2cc37158
Add t-string fixtures for rules that do not need to be modified (#19146)
I used a script to attempt to identify those rules with the following
property: changing f-strings to t-strings in the corresponding fixture
altered the number of lint errors emitted. In other words, those rules
for which f-strings and t-strings are not treated the same in the
current implementation.

This PR documents the subset of such rules where this is fine and no
changes need to be made to the implementation of the rule. Mostly these
are the rules where it is relevant that an f-string evaluates to type
`str` at runtime whereas t-strings do not.

In theory many of these fixtures are not super necessary - it's unlikely
t-strings would be used for most of these. However, the internal
handling of t-strings is tightly coupled with that of f-strings, and may
become even more so as we implement the upcoming changes due to
https://github.com/python/cpython/pull/135996 . So I'd like to keep
these around as regression tests.

Note: The `flake8-bandit` fixtures were already added during the
original t-string implementation.

| Rule(s) | Reason |
| --- | --- |
| [`unused-method-argument`
(`ARG002`)](https://docs.astral.sh/ruff/rules/unused-method-argument/#unused-method-argument-arg002)
| f-strings exempted for msg in `NotImplementedError` not relevant for
t-strings |
| [`logging-f-string`
(`G004`)](https://docs.astral.sh/ruff/rules/logging-f-string/#logging-f-string-g004)
| t-strings cannot be used here |
| [`f-string-in-get-text-func-call`
(`INT001`)](https://docs.astral.sh/ruff/rules/f-string-in-get-text-func-call/#f-string-in-get-text-func-call-int001)
| rule justified by eager evaluation of interpolations |
| [`flake8-bandit`](https://docs.astral.sh/ruff/rules/#flake8-bandit-s)|
rules justified by eager evaluation of interpolations |
| [`single-string-slots`
(`PLC0205`)](https://docs.astral.sh/ruff/rules/single-string-slots/#single-string-slots-plc0205)
| t-strings cannot be slots in general |
| [`unnecessary-encode-utf8`
(`UP012`)](https://docs.astral.sh/ruff/rules/unnecessary-encode-utf8/#unnecessary-encode-utf8-up012)
| cannot encode t-strings |
| [`no-self-use`
(`PLR6301`)](https://docs.astral.sh/ruff/rules/no-self-use/#no-self-use-plr6301)
| f-strings exempted for msg in NotImplementedError not relevant for
t-strings |
| [`pytest-raises-too-broad`
(`PT011`)](https://docs.astral.sh/ruff/rules/pytest-raises-too-broad/) /
[`pytest-fail-without-message`
(`PT016`)](https://docs.astral.sh/ruff/rules/pytest-fail-without-message/#pytest-fail-without-message-pt016)
/ [`pytest-warns-too-broad`
(`PT030`)](https://docs.astral.sh/ruff/rules/pytest-warns-too-broad/#pytest-warns-too-broad-pt030)
| t-strings cannot be empty or used as messages |
| [`assert-on-string-literal`
(`PLW0129`)](https://docs.astral.sh/ruff/rules/assert-on-string-literal/#assert-on-string-literal-plw0129)
| t-strings are not strings and cannot be empty |
| [`native-literals`
(`UP018`)](https://docs.astral.sh/ruff/rules/native-literals/#native-literals-up018)
| t-strings are not native literals |
2025-07-14 09:46:31 -05:00
Dhruv Manilawala 8a217e5920
[ty] Remove `FileLookupError` (#19323)
## Summary

This PR removes the `FileLookupError` as it's not really required. The
original intention was that this would be returned from the `.file`
lookup to the different handlers but we've since moved the logic of
"lookup file and add trace message if file unavailable with the reason"
under the `file_ok` method which all of the handlers use.
2025-07-14 13:35:14 +00:00
Andrew Gallant f7973ac870 [ty] Fix handling of metaclasses in `object.<CURSOR>` completions
Basically, we weren't quite using `Type::member` in every case
correctly. Specifically, this example from @sharkdp:

```
class Meta(type):
    @property
    def meta_attr(self) -> int:
        return 0

class C(metaclass=Meta): ...

C.<CURSOR>
```

While we would return `C.meta_attr` here, we were claiming its type was
`property`. But its type should be `int`.

Ref https://github.com/astral-sh/ruff/pull/19216#discussion_r2197065241
2025-07-14 08:24:23 -04:00
Micha Reiser 3560f86450
[ty] Use an interval map for scopes by expression (#19025) 2025-07-14 13:50:58 +02:00
David Peter f22da352db
[ty] List all `enum` members (#19283)
## Summary

Adds a way to list all members of an `Enum` and implements almost all of
the mechanisms by which members are distinguished from non-members
([spec](https://typing.python.org/en/latest/spec/enums.html#defining-members)).
This has no effect on actual enums, so far.

## Test Plan

New Markdown tests using `ty_extensions.enum_members`.
2025-07-14 13:18:17 +02:00
Micha Reiser cb530a0216
[ty] Handle configuration errors in LSP more gracefully (#19262) 2025-07-14 12:27:52 +02:00
Micha Reiser 90026047f9
[ty] Use python version and path from Python extension (#19012) 2025-07-14 09:47:27 +00:00
w0nder1ng 26f736bc46
[`pep8_naming`] Avoid false positives on standard library functions with uppercase names (`N802`) (#18907)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-14 08:26:57 +00:00
renovate[bot] c9f95e8714
Update Rust crate toml to 0.9.0 (#19320)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [toml](https://redirect.github.com/toml-rs/toml) |
workspace.dependencies | minor | `0.8.11` -> `0.9.0` |

---

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

---

### Release Notes

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

###
[`v0.9.2`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.1...toml-v0.9.2)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.1...toml-v0.9.2)

###
[`v0.9.1`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.0...toml-v0.9.1)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.9.0...toml-v0.9.1)

###
[`v0.9.0`](https://redirect.github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.0)

[Compare
Source](https://redirect.github.com/toml-rs/toml/compare/toml-v0.8.23...toml-v0.9.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:eyJjcmVhdGVkSW5WZXIiOiI0MS4yMy4yIiwidXBkYXRlZEluVmVyIjoiNDEuMjMuMiIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW50ZXJuYWwiXX0=-->

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dhruv Manilawala <dhruvmanila@gmail.com>
2025-07-14 13:11:10 +05:30
Micha Reiser 3da8b51dc1
[ty] Fix server version (#19284) 2025-07-14 09:06:34 +02:00
GiGaGon dca594f89f
[`pyupgrade`] Make example error out-of-the-box (`UP040`) (#19296)
<!--
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 [non-pep695-type-alias
(UP040)](https://docs.astral.sh/ruff/rules/non-pep695-type-alias/#non-pep695-type-alias-up040)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/6beca1be-45cd-4e5a-aafa-6a0584c10d64)
```py
ListOfInt: TypeAlias = list[int]
PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
```

[New example](https://play.ruff.rs/bbad34da-bf07-44e6-9f34-53337e8f57d4)
```py
from typing import Annotated, TypeAlias, TypeAliasType
from annotated_types import Gt

ListOfInt: TypeAlias = list[int]
PositiveInt = TypeAliasType("PositiveInt", Annotated[int, Gt(0)])
```

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

## Test Plan

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

N/A, no functionality/tests affected
2025-07-12 18:39:25 +01:00
GiGaGon 4bc27133a9
[`pyupgrade`] Make example error out-of-the-box (`UP046`) (#19295) 2025-07-12 14:54:56 +01:00
GiGaGon 7154b64248
[`pylint`] Make example error out-of-the-box (`PLE1507`) (#19288)
## Summary

Part of #18972

This PR makes [invalid-envvar-value
(PLE1507)](https://docs.astral.sh/ruff/rules/invalid-envvar-value/#invalid-envvar-value-ple1507)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/a46a9bca-edd5-4474-b20d-e6b6d87291ca)
```py
os.getenv(1)
```

[New example](https://play.ruff.rs/8348d32d-71fa-422c-b228-e2bc343765b1)
```py
import os

os.getenv(1)
```

The "Use instead" section was also updated similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-11 16:08:47 -05:00
GiGaGon 6d01c487a5
[`pyupgrade`] Make example error out-of-the-box (`UP041`) (#19292)
<!--
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 [timeout-error-alias
(UP041)](https://docs.astral.sh/ruff/rules/timeout-error-alias/#timeout-error-alias-up041)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/87e20352-d80a-46ec-98a2-6f6ea700438b)
```py
raise asyncio.TimeoutError
```

[New example](https://play.ruff.rs/d3b95557-46a2-4856-bd71-30d5f3f5ca44)
```py
import asyncio

raise asyncio.TimeoutError
```

## Test Plan

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

N/A, no functionality/tests affected
2025-07-11 16:08:20 -05:00
GiGaGon 6660b11422
[`pyupgrade`] Make example error out-of-the-box (`UP023`) (#19291)
## Summary

Part of #18972

This PR makes [deprecated-c-element-tree
(UP023)](https://docs.astral.sh/ruff/rules/deprecated-c-element-tree/#deprecated-c-element-tree-up023)'s
example error out-of-the-box. I have no clue why the `import
xml.etree.cElementTree` and `from xml.etree import cElementTree` cases
are specifically carved out if they do not have an `as ...`, but the
tests explicitly call this out, and that's how it is in `pyupgrade`'s
source as well.


b5c5f710fc/crates/ruff_linter/resources/test/fixtures/pyupgrade/UP023.py (L23-L31)

[Old example](https://play.ruff.rs/632b8ce1-393d-45e5-9504-5444ae71a0d8)
```py
from xml.etree import cElementTree
```

[New example](https://play.ruff.rs/fef4d378-8c54-41b2-8778-2d02bcbbd7d3)
```py
from xml.etree import cElementTree as ET
```

The "Use instead" section was also updated similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-11 16:07:34 -05:00
Brent Westbrook b5c5f710fc
Render Azure, JSON, and JSON lines output with the new diagnostics (#19133)
## Summary

This was originally stacked on #19129, but some of the changes I made
for JSON also impacted the Azure format, so I went ahead and combined
them. The main changes here are:

- Implementing `FileResolver` for Ruff's `EmitterContext`
- Adding `FileResolver::notebook_index` and `FileResolver::is_notebook`
methods
- Adding a `DisplayDiagnostics` (with an "s") type for rendering a group
of diagnostics at once
- Adding `Azure`, `Json`, and `JsonLines` as new `DiagnosticFormat`s

I tried a couple of alternatives to the `FileResolver::notebook` methods
like passing down the `NotebookIndex` separately and trying to reparse a
`Notebook` from Ruff's `SourceFile`. The latter seemed promising, but
the `SourceFile` only stores the concatenated plain text of the
notebook, not the re-parsable JSON. I guess the current version is just
a variation on passing the `NotebookIndex`, but at least we can reuse
the existing `resolver` argument. I think a lot of this can be cleaned
up once Ruff has its own actual file resolver.

As suggested, I also tried deleting the corresponding `Emitter` files in
`ruff_linter`, but it doesn't look like git was able to follow this as a
rename. It did, however, track that the tests were moved, so the
snapshots should be easy to review.

## Test Plan

Existing Ruff tests ported to tests in `ruff_db`. I think some other
existing ruff tests also cover parts of this refactor.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-11 15:04:46 -04:00
Dan Parizher ee88abf77c
[`flake8_django`] Fix DJ008 false positive for abstract models with type-annotated `abstract` field (#19221)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-11 16:50:59 +00:00
Jack O'Connor 78bd73f25a [ty] add support for `nonlocal` statements 2025-07-11 09:44:54 -07:00
Dan Parizher 110765154f
[`flake8-bugbear`] Fix B017 false negatives for keyword exception arguments (#19217)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-11 16:43:09 +00:00
Dan Parizher 30ee44770d
Fix I002 import insertion after docstring with multiple string statements (#19222) 2025-07-11 18:35:41 +02:00
Dhruv Manilawala fd69533fe5
[ty] Make sure to always respond to client requests (#19277)
## Summary

This PR fixes a bug that didn't return a response to the client if the
document snapshotting failed.

This is resolved by making sure that the server always creates the
document snapshot and embed the any failures inside the snapshot.

Closes: astral-sh/ty#798

## Test Plan

Using the test case as described in the linked issue:



https://github.com/user-attachments/assets/f32833f8-03e5-4641-8c7f-2a536fe2e270
2025-07-11 14:27:27 +00:00
Andrew Gallant 100d765ddf [ty] Document path separator usage in `VendoredFileSystem`
Ref https://github.com/astral-sh/ruff/pull/19266#discussion_r2198530383
2025-07-11 10:06:35 -04:00
Andrew Gallant 6ea231e458 [ty] Add debug output with completion request timings
I had this in a branch somewhere but forgot to get it
merged. So I'm sneaking it in here.

This is useful for very ad hoc performance testing.
2025-07-11 10:06:35 -04:00
Alex Waygood c9df4ddf6a [ty] Add completions for submodule imports
While we did previously support submodule completions via our
`all_members` API, that only works when submodules are attributes of
their parent module. For example, `os.path`. But that didn't work when
the submodule was not an attribute of its parent. For example,
`http.client`. To make the latter work, we read the directory of the
parent module to discover its submodules.
2025-07-11 10:06:35 -04:00
Andrew Gallant 948463aafa [ty] Move `SystemOrVendoredPathRef`
This moves the type and adds a few methods so that it can
be used elsewhere.
2025-07-11 10:06:35 -04:00
Andrew Gallant 729fa12575 [ty] Add "readdir" for vendored file systems
This is mostly just holding a zip file in the right way
to simulate reading a directory. We want this to be able
to discover sub-modules for completions.
2025-07-11 10:06:35 -04:00
Brent Westbrook f14ee9edd5
Use structs for JSON serialization (#19270)
## Summary

See https://github.com/astral-sh/ruff/pull/19133#discussion_r2198413586
for recent discussion. This PR moves to using structs for the types in
our JSON output format instead of the `json!` macro.

I didn't rename any of the `message` references because that should be
handled when rebasing #19133 onto this.

My plan for handling the `preview` behavior with the new diagnostics is
to use a wrapper enum. Something like:

```rust
#[derive(Serialize)]
#[serde(untagged)]
pub(crate) enum JsonDiagnostic<'a> {
    Old(OldJsonDiagnostic<'a>),
}

#[derive(Serialize)]
pub(crate) struct OldJsonDiagnostic<'a> {
    // ...
}
```

Initially I thought I could use a `&dyn Serialize` for the affected
fields, but I see that `Serialize` isn't dyn-compatible in testing this
now.

## Test Plan

Existing tests. One quirk of the new types is that their fields are in
alphabetical order. I guess `json!` sorts the fields alphabetically? The
tests were failing before I sorted the struct fields.

## Other formats

It looks like the `rdjson`, `sarif`, and `gitlab` formats also use
`json!`, so if we decide to merge this, I can do something similar for
those before moving them to the new diagnostic format.
2025-07-11 09:37:44 -04:00
Alex Waygood a67630f907
[ty] Filter out private type aliases from stub files when offering autocomplete suggestions (#19282) 2025-07-11 13:20:16 +00:00
Brent Westbrook 5bc81f26c8
Bump 0.12.3 (#19279) 2025-07-11 09:07:50 -04:00
Brent Westbrook 6908e2682f
Filter `ruff_linter::VERSION` out of SARIF output tests (#19280)
Summary
--

Fixes the test failures in #19279. This is the same variable used to
construct the SARIF output:


350d563c88/crates/ruff_linter/src/message/sarif.rs (L39-L44)

Test Plan
--

Existing tests with the modified filter
2025-07-11 08:55:51 -04:00
Dhruv Manilawala 25c4295564
[ty] Avoid stale diagnostics for open files diagnostic mode (#19273)
## Summary

This PR fixes a bug where in `openFilesOnly` diagnostic mode, VS Code
wouldn't clean up the diagnostics even though the server asked it to by
sending an empty publish diagnostics.

This is not the long-term solution but a quick fix. Ideally, the server
would dynamically register for workspace diagnostics but that requires
listening for `didChangeConfiguration` notification which I'm going to
be working on with https://github.com/astral-sh/ty/issues/82.

## Test Plan

### Before

This uses the latest stable version of ty.


https://github.com/user-attachments/assets/0cc6c513-ccad-4955-a1b6-a0ee242119d6

### After

This uses the debug build of ty from this PR.


https://github.com/user-attachments/assets/e539d569-d852-46a9-bbfc-d54375127c62
2025-07-11 16:29:16 +05:30
Micha Reiser 426fa4bb12
[ty] Add signature help provider to playground (#19276) 2025-07-11 09:58:14 +02:00
UnboundVariable b0b65c24ff
[ty] Initial implementation of signature help provider (#19194)
This PR includes:
* Implemented core signature help logic
* Added new docstring method on Definition that returns a docstring for
function and class definitions
* Modified the display code for Signature that allows a signature string
to be broken into text ranges that correspond to each parameter in the
signature
* Augmented Signature struct so it can track the Definition for a
signature when available; this allows us to find the docstring
associated with the signature
* Added utility functions for parsing parameter documentation from three
popular docstring formats (Google, NumPy and reST)
* Implemented tests for all of the above

"Signature help" is displayed by an editor when you are typing a
function call expression. It is typically triggered when you type an
open parenthesis. The language server provides information about the
target function's signature (or multiple signatures), documentation, and
parameters.

Here is how this appears:


![image](https://github.com/user-attachments/assets/40dce616-ed74-4810-be62-42a5b5e4b334)

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-10 19:32:00 -07:00
Brent Westbrook 08bc6d2589
Add simple integration tests for all output formats (#19265)
Summary
--

I spun this off from #19133 to be sure to get an accurate baseline
before modifying any of the formats. I picked the code snippet to
include a lint diagnostic with a fix, one without a fix, and one syntax
error. I'm happy to expand it if there are any other kinds we want to
test.

I initially passed `CONTENT` on stdin, but I was a bit surprised to
notice that some of our output formats include an absolute path to the
file. I switched to a `TempDir` to use the `tempdir_filter`.

Test Plan
--

New CLI tests
2025-07-10 17:57:48 -04:00
Victor Hugo Gomes f2ae12bab3
[`flake8-return`] Fix false-positive for variables used inside nested functions in `RET504` (#18433)
<!--
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 is the same as #17656.

I accidentally deleted the branch of that PR, so I'm creating a new one.

Fixes #14052

## Test Plan

Add regression tests
<!-- How was it tested? -->
2025-07-10 16:10:22 -04:00
Zanie Blue 965f415212
[ty] Add a `--quiet` mode (#19233)
Adds a `--quiet` flag which silences diagnostic, warning logs, and
messages like "all checks passed" while retaining summary messages that
indicate problems, e.g., the number of diagnostics.

I'm a bit on the fence regarding filtering out warning logs, because it
can omit important details, e.g., the message that a fatal diagnostic
was encountered. Let's discuss that in
https://github.com/astral-sh/ruff/pull/19233#discussion_r2195408693

The implementation recycles the `Printer` abstraction used in uv, which
is intended to replace all direct usage of `std::io::stdout`. See
https://github.com/astral-sh/ruff/pull/19233#discussion_r2195140197

I ended up futzing with the progress bar more than I probably should
have to ensure it was also using the printer, but it doesn't seem like a
big deal. See
https://github.com/astral-sh/ruff/pull/19233#discussion_r2195330467

Closes https://github.com/astral-sh/ty/issues/772
2025-07-10 09:40:47 -05:00
frank 83b5bbf004
Treat form feed as valid whitespace before a line continuation (#19220)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-10 14:09:34 +00:00
Micha Reiser 87f6f08ef5
[ty] Make `check_file` a salsa query (#19255)
## Summary
We noticed that all files get reparsed when workspace diagnostics are
enabled.

I realised that this is because `check_file_impl` access the parsed
module but itself isn't a salsa query.
This pr makes `check_file_impl` a salsa query, so that we only access
the `parsed_module` when the file actually changed. I decided to remove
the salsa query from `check_types` because most functions it calls are
salsa queries itself and having both `check_types` and `check_file` as
salsa querise has the downside that we double cache the diagnostics.

## Test Plan

**Before**

```
2025-07-10 12:54:16.620766000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0c))}: File `/Users/micha/astral/test/yaml/yaml-stubs/__init__.pyi` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.621942000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c13))}: File `/Users/micha/astral/test/ignore2 2/nested-repository/main.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.622107000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c09))}: File `/Users/micha/astral/test/notebook.ipynb` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.622357000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c04))}: File `/Users/micha/astral/test/no-trailing.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.622634000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c02))}: File `/Users/micha/astral/test/simple.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.623056000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c07))}: File `/Users/micha/astral/test/open/more.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.623254000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c11))}: File `/Users/micha/astral/test/ignore-bug/backend/src/subdir/log/some_logging_lib.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.623450000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0f))}: File `/Users/micha/astral/test/yaml/tomllib/__init__.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.624599000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c05))}: File `/Users/micha/astral/test/create.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.624784000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c00))}: File `/Users/micha/astral/test/lib.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.624911000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0a))}: File `/Users/micha/astral/test/sub/test.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625032000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c12))}: File `/Users/micha/astral/test/ignore2/nested-repository/main.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625101000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c08))}: File `/Users/micha/astral/test/open/test.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625227000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c03))}: File `/Users/micha/astral/test/pseudocode_with_bom.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625353000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0b))}: File `/Users/micha/astral/test/yaml/yaml-stubs/loader.pyi` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625543000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c01))}: File `/Users/micha/astral/test/test_trailing.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625616000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0d))}: File `/Users/micha/astral/test/yaml/tomllib/_re.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625667000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c06))}: File `/Users/micha/astral/test/yaml/main.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.625779000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c10))}: File `/Users/micha/astral/test/yaml/tomllib/_types.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.627526000  WARN request{id=19 method="workspace/diagnostic"}:Project::check:check_file{file=file(Id(c0e))}: File `/Users/micha/astral/test/yaml/tomllib/_parser.py` was reparsed after being collected in the current Salsa revision
2025-07-10 12:54:16.627959000 DEBUG request{id=19 method="workspace/diagnostic"}:Project::check: Checking all files took 0.007s
```

Now, no more logs regarding reparsing
2025-07-10 18:46:56 +05:30
Alex Waygood 59114d0301
[ty] Consolidate submodule resolving code between `types.rs` and `ide_support.rs` (#19256) 2025-07-10 13:10:09 +00:00
Micha Reiser 492f5bf2aa
[ty] Remove countme from salsa-structs (#19257) 2025-07-10 11:45:09 +00:00
Alex Waygood 934aaa23f3
[ty] Improve and document equivalence for module-literal types (#19243) 2025-07-10 09:11:10 +00:00
Alex Waygood 59aa869724
[ty] Optimize protocol subtyping by removing expensive and unnecessary equivalence check from the top of `Type::has_relation_to()` (#19230) 2025-07-10 09:42:27 +01:00
Micha Reiser 5fb2fb916b
[ty] Add completion kind to playground (#19251) 2025-07-10 07:41:59 +00:00
Micha Reiser 3926dd8424
[ty] Add semantic token provider to playground (#19232) 2025-07-10 07:50:28 +02:00
Dan Parizher 221edcba5c
[`pyupgrade`] Keyword arguments in `super` should suppress the `UP008` fix (#19131)
## Summary

Fixes #19096
2025-07-09 15:13:22 -04:00
chiri beb98dae7c
[`flake8-use-pathlib`] Add autofixes for `PTH100`, `PTH106`, `PTH107`, `PTH108`, `PTH110`, `PTH111`, `PTH112`, `PTH113`, `PTH114`, `PTH115`, `PTH117`, `PTH119`, `PTH120` (#19213)
## Summary

Part of #2331

## Test Plan

update snapshots for preview mode
2025-07-09 14:54:33 -04:00
GiGaGon a18f76158d
[`flake8-bandit`] Make example error out-of-the-box (`S412`) (#19241)
<!--
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 [suspicious-httpoxy-import
(S412)](https://docs.astral.sh/ruff/rules/suspicious-httpoxy-import/#suspicious-httpoxy-import-s412)'s
example error out-of-the-box. Since the checked imports are classes
instead of modules, the example isn't valid. See #19009 for more details
```
PS ~>py -c "import wsgiref.handlers.CGIHandler"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
    import wsgiref.handlers.CGIHandler
ModuleNotFoundError: No module named 'wsgiref.handlers.CGIHandler'; 'wsgiref.handlers' is not a package
PS ~>py -c "from wsgiref.handlers import CGIHandler"
PS ~>
```

[Old example](https://play.ruff.rs/bf48c901-6a46-4795-ba1d-c6af79d5c96e)
```py
import wsgiref.handlers.CGIHandler
```

[New example](https://play.ruff.rs/1f0e1e60-1f0f-484a-9a17-2d0290a68f2a)
```py
from wsgiref.handlers import CGIHandler
```

## Test Plan

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

N/A, no functionality/tests affected
2025-07-09 14:25:27 -04:00
GiGaGon 8f400bb37a
[`pydoclint`] Make example error out-of-the-box (`DOC501`) (#19218)
<!--
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 [docstring-missing-exception
(DOC501)](https://docs.astral.sh/ruff/rules/docstring-missing-exception/#docstring-missing-exception-doc501)'s
example error out-of-the-box. Since the exceptions in the function body
need to undergo name resolution to figure out if one of them is
`NotImplementedError`, `DOC501` won't lint if the raised name is not
defined. This could be considered a limitation, but should be fine since
`F821` already covers undefined names. I did discover a different edge
case, but it's not relevant to the example.

[Old example](https://play.ruff.rs/d213e87d-e5c7-49d8-a908-931f61f06055)
```py
def calculate_speed(distance: float, time: float) -> float:
    """Calculate speed as distance divided by time.

    Args:
        distance: Distance traveled.
        time: Time spent traveling.

    Returns:
        Speed as distance divided by time.
    """
    try:
        return distance / time
    except ZeroDivisionError as exc:
        raise FasterThanLightError from exc
```

[New example](https://play.ruff.rs/cb41e0b7-b950-4fa0-842d-cecab9c8e842)
```py
class FasterThanLightError(ArithmeticError): ...


def calculate_speed(distance: float, time: float) -> float:
    """Calculate speed as distance divided by time.

    Args:
        distance: Distance traveled.
        time: Time spent traveling.

    Returns:
        Speed as distance divided by time.
    """
    try:
        return distance / time
    except ZeroDivisionError as exc:
        raise FasterThanLightError from exc
```

The "Use instead" section was also updated similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-09 12:59:31 -04:00
Andrew Gallant 1eff0300d3 [ty] Add "kind" to completion suggestions
This makes use of the new `Type` field on `Completion` to figure out the
"kind" of a `Completion`.

The mapping here is perhaps a little suspect for some cases.

Closes astral-sh/ty#775
2025-07-09 12:03:56 -04:00
Andrew Gallant fea84e8777 [ty] Add type information to `all_members` API
Since we generally need (so far) to get the type information of each
suggestion to figure out its boundness anyway, we might as well expose
it here. Completions want to use this information to enhance the
metadata on each suggestion for a more pleasant user experience.

For the most part, this was pretty straight-forward. The most exciting
part was in computing the types for instance attributes. I'm not 100%
sure it's correct or is the best way to do it.
2025-07-09 12:03:56 -04:00
Andrew Gallant 79fe538458 [ty] Expand API of `all_members` to return a struct
This commit doesn't change any behavior, but makes it so `all_members`
returns a `Vec<Member>` instead of `Vec<Name>`, where a `Member`
contains a `Name`. This gives us an expansion point to include other
data (such as the type of the `Name`).
2025-07-09 12:03:56 -04:00
Micha Reiser 35a33f045e
[ty] Merge `ty_macros` into `ruff_macros` (#19229) 2025-07-09 11:28:21 +00:00
Matthew Mckee f32f7a3b48
[ty] Fix `ClassLiteral.into_callable` for dataclasses (#19192)
## Summary

Change `ClassLiteral.into_callable` to also look for `__init__` functions
of type `Type::Callable` (such as synthesized `__init__` functions of
dataclasses).

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

## Test Plan

Add subtype test

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-09 10:04:55 +02:00
David Peter 68106dd631
[ty] `dataclasses.field` support (#19140)
## Summary

Add an initial set of tests for `dataclasses.field`.
2025-07-09 09:18:08 +02:00
David Peter ab3af924ef
[ty] Fix panic for attribute expressions with empty value (#19069)
## Summary

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

## Test Plan

Added corpus test
2025-07-09 08:46:33 +02:00
Matthew Mckee 05139a323b
[ty] Return `CallableType` from `BoundMethodType.into_callable_type` (#19193) 2025-07-08 20:33:43 +01:00
Dan Parizher 5eb5ec987d
[`flake8-bugbear`] Support non-context-manager calls in `B017` (#19063)
## Summary

Fixes #19050

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-08 15:04:55 -04:00
David Peter 1a099886ab
[ty] Improved diagnostic for reassignments of `Final` symbols (#19214)
## Summary

Implement [this
suggestion](https://github.com/astral-sh/ruff/pull/19178#discussion_r2192658146)
by @AlexWaygood.


![image](https://github.com/user-attachments/assets/f183d691-ef6e-43a2-b005-3a32205bc408)
2025-07-08 20:29:07 +02:00
David Peter a8f2c26143
[ty] Use full range for assignment definitions (#19211)
## Summary

Fix the `full_range` function for (annotated) assignment definition
kinds.

## Test Plan

Update snapshot tests
2025-07-08 19:51:09 +02:00
Junhson Jean-Baptiste fda188953f
[`pylint`] Update `missing-maxsplit-arg` docs and error to suggest proper usage (`PLC0207`) (#18949)
<!--
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? -->

Fix #18383 by updating the documentation and error message to explain
that users should use `rsplit` in order to access the last element of
the result with `maxsplit=1`

## Test Plan

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

Only documentation and an error message was changed. As such, snapshots
were updated to reflect the new error message. With this change, all
existing tests pass.
2025-07-08 12:53:23 -04:00
Alex Waygood 7533a0bfdb
[ty] Upgrade mypy_primer (#19207) 2025-07-08 15:56:54 +01:00
Charlie Marsh 3ee3434187
Auto-generate environment variable references for ty (#19205)
## Summary

This PR mirrors the environment variable implementation we have in uv:
efc361223c/crates/uv-static/src/env_vars.rs (L6-L7).

See: https://github.com/astral-sh/ty/issues/773.
2025-07-08 10:48:31 -04:00
David Peter 149350bf39
[ty] Enforce `typing.Final` (#19178)
## Summary

Emit a diagnostic when a `Final`-qualified symbol is modified. This
first iteration only works for name targets. Tests with TODO comments
were added for attribute assignments as well.

related ticket: https://github.com/astral-sh/ty/issues/158

## Ecosystem impact

Correctly identified [modification of a `Final`
symbol](7b4164a5f2/sphinx/__init__.py (L44))
(behind a `# type: ignore`):
```diff
- warning[unused-ignore-comment] sphinx/__init__.py:44:56: Unused blanket `type: ignore` directive
```
And the same
[here](5471a37e82/src/trio/_core/_run.py (L128)):
```diff
- warning[unused-ignore-comment] src/trio/_core/_run.py:128:45: Unused blanket `type: ignore` directive
```

## Test Plan

New Markdown tests
2025-07-08 16:26:09 +02:00
Aria Desires 6a42d28867
[ty] Do not report settings diagnostics in `check_file` (#19206)
This is the trivial first part of
https://github.com/astral-sh/ty/issues/613

Ideally we should surface these elsewhere, but this is definitely Not
the place to surface them.
2025-07-08 10:18:32 -04:00
David Peter ce2bdb9357
[ty] Conditionally defined dataclass fields (#19197)
## Summary

Fixes a bug where conditionally defined dataclass fields were previously
ignored.

Thanks to @lipefree for reporting this.

## Test Plan

New Markdown tests
2025-07-08 16:16:50 +02:00
GiGaGon d78d10dd94
[`pycodestyle`] Make example not raise unnecessary `SyntaxError` (`E114`) (#19190)
<!--
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 [indentation-with-invalid-multiple-comment
(E114)](https://docs.astral.sh/ruff/rules/indentation-with-invalid-multiple-comment/#indentation-with-invalid-multiple-comment-e114)'s
example not raise a syntax error by adding a 4 space indented `...`. The
example still gave `E114` without this, but adding the `...` both makes
the change in indentation of the comment clearer, and makes it not give
a `SyntaxError`.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-08 10:00:14 -04:00
GiGaGon 36276143be
[`pycodestyle`] Make example error out-of-the-box (`E272`) (#19191)
<!--
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 [multiple-spaces-before-keyword
(E272)](https://docs.astral.sh/ruff/rules/multiple-spaces-before-keyword/#multiple-spaces-before-keyword-e272)'s
example error out-of-the-box. Since `True` is also a keyword, the old
example raises `E271` instead.

[Old example](https://play.ruff.rs/23ec3774-5038-471c-be3f-1c1e36f85cbb)
```py
True  and False
```

[New example](https://play.ruff.rs/d77432e2-fd99-4db2-9cd0-bc08675c0aca)
```py
x  and y
```

The "Use instead" section was also updated similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-08 09:58:04 -04:00
Brent Westbrook 2643dc5b7a
Rename `Diagnostic::syntax_error` methods, separate `Ord` implementation (#19179)
## Summary

This PR addresses some additional feedback on #19053:

- Renaming the `syntax_error` methods to `invalid_syntax` to match the
lint id
- Moving the standalone `diagnostic_from_violation` function to
`Violation::into_diagnostic`
- Removing the `Ord` and `PartialOrd` implementations from `Diagnostic`
in favor of `Diagnostic::start_ordering`

## Test Plan

Existing tests

## Additional Follow-ups

Besides these, I also put the following comments on my todo list, but
they seemed like they might be big enough to have their own PRs:

- [Use `LintId::IOError` for IO
errors](https://github.com/astral-sh/ruff/pull/19053#discussion_r2189425922)
- [Move `Fix` and
`Edit`](https://github.com/astral-sh/ruff/pull/19053#discussion_r2189448647)
- [Avoid so many
unwraps](https://github.com/astral-sh/ruff/pull/19053#discussion_r2189465980)
2025-07-08 09:54:19 -04:00
justin 738692baff
[ty] Fix __setattr__ call check precedence during attribute assignment (#18347)
## Summary

Related:

- https://github.com/astral-sh/ty/issues/111
- https://github.com/astral-sh/ruff/pull/17974#discussion_r2108527106

Previously, when validating an attribute assignment, a `__setattr__`
call check was only done if the attribute wasn't found as either a class
member or instance member

This PR changes the `__setattr__` call check to be attempted first,
prior to the "[normal
mechanism](https://docs.python.org/3/reference/datamodel.html#object.__setattr__)",
as a defined `__setattr__` should take precedence over setting an
attribute on the instance dictionary directly.

if the return type of `__setattr__` is `Never`, an `invalid-assignment`
diagnostic is emitted

Once this is merged, a subsequent PR will synthesize a `__setattr__`
method with a `Never` return type for frozen dataclasses.

## Test Plan

Existing tests + mypy_primer

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-08 15:34:34 +02:00
David Peter 9a4b85d845
[ty] Add tests for dataclass fields annotated with `Final` (#19202)
## Summary

Adds some tests for dataclass fields that are annotated with `Final`
(see comment
[here](https://github.com/astral-sh/ruff/pull/15768#issuecomment-3044737645)).
Turns out that nothing is needed here, everything already works as
expected (apart from the fact that we can assign to `Final` fields,
which is tracked in https://github.com/astral-sh/ty/issues/158

## Test Plan

New Markdown tests
2025-07-08 12:33:46 +00:00
David Peter 6d8c84bde9
[ty] Clarify diagnostic message (#19203)
This diagnostic message was missing the word "type"
2025-07-08 14:21:20 +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
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] 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
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
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
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
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