Commit Graph

8010 Commits

Author SHA1 Message Date
Micha Reiser 365f521c37
[ty] Fix incorrect docstring in call signature completion (#20021)
## Summary

This PR fixes https://github.com/astral-sh/ty/issues/1071

The core issue is that `CallableType` is a salsa interned but
`Signature` (which `CallableType` stores) ignores the `Definition` in
its `Eq` and `Hash` implementation.

This PR tries to simplest fix by removing the custom `Eq` and `Hash`
implementation. The main downside of this fix is that it can increase
memory usage because `CallableType`s that are equal except for their
`Definition` are now interned separately.

The alternative is to remove `Definition` from `CallableType` and
instead, call `bindings` directly on the callee (call_expression.func).
However, this would require
addressing the TODO 

here
39ee71c2a5/crates/ty_python_semantic/src/types.rs (L4582-L4586)

This might probably be worth addressing anyway, but is the more involved
fix. That's why I opted for removing the custom `Eq` implementation.

We already "ignore" the definition during normalization, thank's to
Alex's work in https://github.com/astral-sh/ruff/pull/19615

## Test Plan



https://github.com/user-attachments/assets/248d1cb1-12fd-4441-adab-b7e0866d23eb
2025-08-21 16:36:40 -04:00
Aria Desires fc5321e000
[ty] fix GotoTargets for keyword args in nested function calls (#20013)
While implementing similar logic for initializers I noticed that this
code appeared to be walking the ancestors in the wrong direction, and so
if you have nested function calls it would always grab the outermost one
instead of the closest-ancestor.

The four copies of the test are because there's something really evil in
our caching that can't seem to be demonstrated in our cursor testing
framework, which I'm filing a followup for.
2025-08-21 20:19:52 +00:00
Dylan c68ff8d90b
Bump 0.12.10 (#20025) 2025-08-21 13:09:31 -05:00
Andrew Gallant 5931a5207d [ty] Stop running every mdtest twice
This was an accidental oversight introduced in commit
468eb37d75.
2025-08-21 13:37:08 -04:00
Brent Westbrook 692be72f5a
Move diff rendering to `ruff_db` (#20006)
Summary
--

This is a preparatory PR in support of #19919. It moves our `Diff`
rendering code from `ruff_linter` to `ruff_db`, where we have direct
access to the `DiagnosticStylesheet` used by our other diagnostic
rendering code. As shown by the tests, this shouldn't cause any visible
changes. The colors aren't exactly the same, as I note in a TODO
comment, but I don't think there's any existing way to see those, even
in tests.

The `Diff` implementation is mostly unchanged. I just switched from a
Ruff-specific `SourceFile` to a `DiagnosticSource` (removing an
`expect_ruff_source_file` call) and updated the `LineStyle` struct and
other styling calls to use `fmt_styled` and our existing stylesheet.

In support of these changes, I added three styles to our stylesheet:
`insertion` and `deletion` for the corresponding diff operations, and
`underline`, which apparently we _can_ use, as I hoped on Discord. This
isn't supported in all terminals, though. It worked in ghostty but not
in st for me.

I moved the `calculate_print_width` function from the now-deleted
`diff.rs` to a method on `OneIndexed`, where it was available everywhere
we needed it. I'm not sure if that's desirable, or if my other changes
to the function are either (using `ilog10` instead of a loop). This does
make it `const` and slightly simplifies things in my opinion, but I'm
happy to revert it if preferred.

I also inlined a version of `show_nonprinting` from the
`ShowNonprinting` trait in `ruff_linter`:


f4be05a83b/crates/ruff_linter/src/text_helpers.rs (L3-L5)

This trait is now only used in `source_kind.rs`, so I'm not sure it's
worth having the trait or the macro-generated implementation (which is
only called once). This is obviously closely related to our unprintable
character handling in diagnostic rendering, but the usage seems
different enough not to try to combine them.


f4be05a83b/crates/ruff_db/src/diagnostic/render.rs (L990-L998)

We could also move the trait to another crate where we can use it in
`ruff_db` instead of inlining here, of course.

Finally, this PR makes `TextEmitter` a very thin wrapper around a
`DisplayDiagnosticsConfig`. It's still used in a few places, though,
unlike the other emitters we've replaced, so I figured it was worth
keeping around. It's a pretty nice API for setting all of the options on
the config and then passing that along to a `DisplayDiagnostics`.

Test Plan
--

Existing snapshot tests with diffs
2025-08-21 09:47:00 -04:00
Douglas Creager 14fe1228e7
[ty] Perform assignability etc checks using new `Constraints` trait (#19838)
"Why would you do this? This looks like you just replaced `bool` with an
overly complex trait"

Yes that's correct!

This should be a no-op refactoring. It replaces all of the logic in our
assignability, subtyping, equivalence, and disjointness methods to work
over an arbitrary `Constraints` trait instead of only working on `bool`.

The methods that `Constraints` provides looks very much like what we get
from `bool`. But soon we will add a new impl of this trait, and some new
methods, that let us express "fuzzy" constraints that aren't always true
or false. (In particular, a constraint will express the upper and lower
bounds of the allowed specializations of a typevar.)

Even once we have that, most of the operations that we perform on
constraint sets will be the usual boolean operations, just on sets.
(`false` becomes empty/never; `true` becomes universe/always; `or`
becomes union; `and` becomes intersection; `not` becomes negation.) So
it's helpful to have this separate PR to refactor how we invoke those
operations without introducing the new functionality yet.

Note that we also have translations of `Option::is_some_and` and
`is_none_or`, and of `Iterator::any` and `all`, and that the `and`,
`or`, `when_any`, and `when_all` methods are meant to short-circuit,
just like the corresponding boolean operations. For constraint sets,
that depends on being able to implement the `is_always` and `is_never`
trait methods.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-08-21 09:30:09 -04:00
Micha Reiser 045cba382a
[ty] Use `dedent` in cursor tests (#20019) 2025-08-21 10:31:54 +02:00
Brent Westbrook a5cbca156c
Fix rust feature activation (#20012) 2025-08-21 09:26:06 +02:00
Dhruv Manilawala d43a3d34dd
[ty] Avoid unnecessary argument type expansion (#19999)
## Summary

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

This PR adds a heuristic to avoid argument type expansion if it's going
to eventually lead to no matching overload.

This is done by checking whether the non-expandable argument types are
assignable to the corresponding annotated parameter type. If one of them
is not assignable to all of the remaining overloads, then argument type
expansion isn't going to help.

## Test Plan

Add mdtest that would otherwise take a long time because of the number
of arguments that it would need to expand (30).
2025-08-21 06:13:11 +00:00
Aria Desires 99111961c0
[ty] Add link for namespaces being partial (#20015)
As requested
2025-08-20 21:28:57 -07:00
Aria Desires 859475f017
[ty] add docstrings to completions based on type (#20008)
This is a fairly simple but effective way to add docstrings to like 95%
of completions from initial experimentation.

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

Although ironically this approach *does not* work specifically for
`print` and I haven't looked into why.
2025-08-20 17:00:09 -04:00
Igor Drokin 7b75aee21d
[`pyupgrade`] Avoid reporting `__future__` features as unnecessary when they are used (`UP010`) (#19769)
## Summary
Resolves #19561

Fixes the [unnecessary-future-import
(UP010)](https://docs.astral.sh/ruff/rules/unnecessary-future-import/)
rule to correctly identify when imported __future__ modules are actually
used in the code, preventing false positives.

I assume there is no way to check usage in `analyze::statements`,
because we don't have any usage bindings for imports. To determine
unused imports, we have to fully scan the file to create bindings and
then check usage, similar to [unused-import
(F401)](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401).
So, `Rule::UnnecessaryFutureImport` was moved from the
`analyze::statements` to the `analyze::deferred_scopes` stage. This
caused the need to change the logic of future import handling to a
bindings-based approach.

Also, the diagnostic report was changed.
Before
```
  |
1 | from __future__ import nested_scopes, generators
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ UP010
```
after
```
  |
1 | from __future__ import nested_scopes, generators
  |                        ^^^^^^^^^^^^^ UP010
```

I believe this is the correct way, because `generators` may be used, but
`nested_scopes` is not.

### Special case
I've found out about some specific case.
```python
from __future__ import nested_scopes

nested_scopes = 1
```
Here we can treat `nested_scopes` as an unused import because the
variable `nested_scopes` shadows it and we can safely remove the future
import (my fix does it).

But
[F401](https://docs.astral.sh/ruff/rules/unused-import/#unused-import-f401)
not triggered for such case
([sandbox](https://play.ruff.rs/296d9c7e-0f02-4659-b0c0-78cc21f3de76))
```
from foo import print_function

print_function = 1
```
In my mind, `print_function` here is an unused import and should be
deleted (my IDE highlight it). What do you think?

## Test Plan

Added test cases and snapshots:
- Split test file into separate _0 and _1 files for appropriate checks.
- Added test cases to verify fixes when future module are used.

---------

Co-authored-by: Igor Drokin <drokinii1017@gmail.com>
2025-08-20 15:22:03 -04:00
chiri d04dcd991b
[`flake8-use-pathlib`] Add fixes for `PTH102` and `PTH103` (#19514)
## Summary

Part of https://github.com/astral-sh/ruff/issues/2331

## Test Plan

<!-- How was it tested? -->
`cargo nextest run flake8_use_pathlib`
2025-08-20 14:36:07 -04:00
Leandro Braga 39ee71c2a5
[ty] correctly ignore field specifiers when not specified (#20002)
This commit corrects the type checker's behavior when handling
`dataclass_transform` decorators that don't explicitly specify
`field_specifiers`. According to [PEP 681 (Data Class
Transforms)](https://peps.python.org/pep-0681/#dataclass-transform-parameters),
when `field_specifiers` is not provided, it defaults to an empty tuple,
meaning no field specifiers are supported and
`dataclasses.field`/`dataclasses.Field` calls should be ignored.

Fixes https://github.com/astral-sh/ty/issues/980
2025-08-20 11:33:23 -07:00
Brent Westbrook 1a38831d53
`Option::unwrap` is now const (#20007)
Summary
--

I noticed while working on #20006 that we had a custom `unwrap` function
for `Option`. This has been const on stable since 1.83
([docs](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap),
[release notes](https://blog.rust-lang.org/2024/11/28/Rust-1.83.0/)), so
I think it's safe to use now. I grepped a bit for related todos and
found this one for `AsciiCharSet` but no others.

Test Plan
--

Existing tests
2025-08-20 13:40:49 -04:00
Andrew Gallant ddd4bab67c [ty] Re-arrange "list modules" implementation for Salsa caching
This basically splits `list_modules` into a higher level "aggregation"
routine and a lower level "get modules for one search path" routine.
This permits Salsa to cache the lower level components, e.g., many
search paths refer to directories that rarely change. This saves us
interaction with the system.

This did require a fair bit of surgery in terms of being careful about
adding file roots. Namely, now that we rely even more on file roots
existing for correct handling of cache invalidation, there were several
spots in our code that needed to be updated to add roots (that we
weren't previously doing). This feels Not Great, and it would be better
if we had some kind of abstraction that handled this for us. But it
isn't clear to me at this time what that looks like.
2025-08-20 10:41:47 -04:00
Andrew Gallant 468eb37d75 [ty] Test "list modules" versus "resolve module" in every mdtest
This ensures there is some level of consistency between the APIs.

This did require exposing a couple more things on `Module` for good
error messages. This also motivated a switch to an interned struct
instead of a tracked struct. This ensures that `list_modules` and
`resolve_modules` reuse the same `Module` values when the inputs are the
same.

Ref https://github.com/astral-sh/ruff/pull/19883#discussion_r2272520194
2025-08-20 10:27:54 -04:00
Andrew Gallant 2e9c241d7e [ty] Wire up "list modules" API to make module completions work
This makes `import <CURSOR>` and `from <CURSOR>` completions work.

This also makes `import os.<CURSOR>` and `from os.<CURSOR>`
completions work. In this case, we are careful to only offer
submodule completions.
2025-08-20 10:27:54 -04:00
Andrew Gallant 05478d5cc7 [ty] Tweak some completion tests
These tests were added as a regression check that a panic
didn't occur. So we were asserting a bit more than necessary.
In particular, these will soon return completions for modules,
which creates large snapshots that we don't need.

So modify these to just check there is sensible output that
doesn't panic.
2025-08-20 10:27:54 -04:00
Andrew Gallant 4db20f459c [ty] Add "list modules" implementation
The actual implementation wasn't too bad. It's not long
but pretty fiddly. I copied over the tests from the existing
module resolver and adapted them to work with this API. Then
I added a number of my own tests as well.
2025-08-20 10:27:54 -04:00
Andrew Gallant ec7c2efef9 [ty] Lightly expose `FileModule` and `NamespacePackage` fields
This will make it easier to emit this info into snapshots for
testing.
2025-08-20 10:27:54 -04:00
Andrew Gallant 79b2754215 [ty] Add some more helper routines to `ModulePath` 2025-08-20 10:27:54 -04:00
Andrew Gallant a0ddf1f7c4 [ty] Fix a bug when converting `ModulePath` to `ModuleName`
Previously, if the module was just `foo-stubs`, we'd skip over
stripping the `-stubs` suffix which would lead to us returning
`None`.

This function is now a little convoluted and could be simpler
if we did an intermediate allocation. But I kept the iterative
approach and added a special case to handle `foo-stubs`.
2025-08-20 10:27:54 -04:00
Andrew Gallant 5b00ec981b [ty] Split out another constructor for `ModuleName`
This makes it a little more flexible to call. For example,
we might have a `StmtImport` and not a `StmtImportFrom`.
2025-08-20 10:27:54 -04:00
Andrew Gallant 306ef3bb02 [ty] Add stub-file tests to existing module resolver
These tests capture existing behavior.

I added these when I stumbled upon what I thought was an
oddity: we prioritize `foo.pyi` over `foo.py`, but
prioritize `foo/__init__.py` over `foo.pyi`.

(I plan to investigate this more closely in follow-up
work. Particularly, to look at other type checkers. It
seems like we may want to change this to always prioritize
stubs.)
2025-08-20 10:27:54 -04:00
Andrew Gallant a4cd13c6e2 [ty] Expose some routines in the module resolver
We'll want to use these when implementing the
"list modules" API.
2025-08-20 10:27:54 -04:00
Andrew Gallant e0c98874e2 [ty] Add more path helper functions
This makes it easier to do exhaustive case analysis
on a `SearchPath` depending on whether it is a vendored
or system path.
2025-08-20 10:27:54 -04:00
Andrey f4be05a83b
[`flake8-annotations`] Remove unused import in example (`ANN401`) (#20000)
## Summary

Remove unused import in the  "Use instead" example.

## Test Plan

It's just a text description, no test needed
2025-08-20 09:19:18 -04:00
Aria Desires 1d2128f918
[ty] distinguish base conda from child conda (#19990)
This is a port of the logic in https://github.com/astral-sh/uv/pull/7691

The basic idea is we use CONDA_DEFAULT_ENV as a signal for whether
CONDA_PREFIX is just the ambient system conda install, or the user has
explicitly activated a custom one. If the former, then the conda is
treated like a system install (having lowest priority). If the latter,
the conda is treated like an activated venv (having priority over
everything but an Actual activated venv).

Fixes https://github.com/astral-sh/ty/issues/611
2025-08-20 09:07:42 -04:00
Dhruv Manilawala f019cfd15f
[ty] Use specialized parameter type for overload filter (#19964)
## Summary

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

(This turned out to be simpler that I thought :))

## Test Plan

Update existing test cases.

### Ecosystem report

Most of them are basically because ty has now started inferring more
precise types for the return type to an overloaded call and a lot of the
types are defined using type aliases, here's some examples:

<details><summary>Details</summary>
<p>

> attrs (https://github.com/python-attrs/attrs)
> + tests/test_make.py:146:14: error[unresolved-attribute] Type
`Literal[42]` has no attribute `default`
> - Found 555 diagnostics
> + Found 556 diagnostics

This is accurate now that we infer the type as `Literal[42]` instead of
`Unknown` (Pyright infers it as `int`)

> optuna (https://github.com/optuna/optuna)
> + optuna/_gp/search_space.py:181:53: error[invalid-argument-type]
Argument to function `_round_one_normalized_param` is incorrect:
Expected `tuple[int | float, int | float]`, found `tuple[Unknown |
ndarray[Unknown, <class 'float'>], Unknown | ndarray[Unknown, <class
'float'>]]`
> + optuna/_gp/search_space.py:181:83: error[invalid-argument-type]
Argument to function `_round_one_normalized_param` is incorrect:
Expected `int | float`, found `Unknown | ndarray[Unknown, <class
'float'>]`
> + tests/gp_tests/test_search_space.py:109:13:
error[invalid-argument-type] Argument to function
`_unnormalize_one_param` is incorrect: Expected `tuple[int | float, int
| float]`, found `Unknown | ndarray[Unknown, <class 'float'>]`
> + tests/gp_tests/test_search_space.py:110:13:
error[invalid-argument-type] Argument to function
`_unnormalize_one_param` is incorrect: Expected `int | float`, found
`Unknown | ndarray[Unknown, <class 'float'>]`
> - Found 559 diagnostics
> + Found 563 diagnostics

Same as above where ty is now inferring a more precise type like
`Unknown | ndarray[tuple[int, int], <class 'float'>]` instead of just
`Unknown` as before

> jinja (https://github.com/pallets/jinja)
> + src/jinja2/bccache.py:298:39: error[invalid-argument-type] Argument
to bound method `write_bytecode` is incorrect: Expected `IO[bytes]`,
found `_TemporaryFileWrapper[str]`
> - Found 186 diagnostics
> + Found 187 diagnostics

This requires support for type aliases to match the correct overload.

> hydra-zen (https://github.com/mit-ll-responsible-ai/hydra-zen)
> + src/hydra_zen/wrapper/_implementations.py:945:16:
error[invalid-return-type] Return type does not match returned value:
expected `DataClass_ | type[@Todo(type[T] for protocols)] | ListConfig |
DictConfig`, found `@Todo(unsupported type[X] special form) | (((...) ->
Any) & dict[Unknown, Unknown]) | (DataClass_ & dict[Unknown, Unknown]) |
dict[Any, Any] | (ListConfig & dict[Unknown, Unknown]) | (DictConfig &
dict[Unknown, Unknown]) | (((...) -> Any) & list[Unknown]) | (DataClass_
& list[Unknown]) | list[Any] | (ListConfig & list[Unknown]) |
(DictConfig & list[Unknown])`
> + tests/annotations/behaviors.py:60:28: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/behaviors.py:64:21: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/declarations.py:167:17: error[call-non-callable]
Object of type `Path` is not callable
> + tests/annotations/declarations.py:524:17:
error[unresolved-attribute] Type `<class 'int'>` has no attribute
`_target_`
> - Found 561 diagnostics
> + Found 566 diagnostics

Same as above, this requires support for type aliases to match the
correct overload.

> paasta (https://github.com/yelp/paasta)
> + paasta_tools/utils.py:4188:19: warning[redundant-cast] Value is
already of type `list[str]`
> - Found 888 diagnostics
> + Found 889 diagnostics

This is correct.

> colour (https://github.com/colour-science/colour)
> + colour/plotting/diagrams.py:448:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/diagrams.py:462:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/models.py:419:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:230:9: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:474:13: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:495:17: error[invalid-argument-type]
Argument to bound method `__init__` is incorrect: Expected
`Sequence[@Todo(Support for `typing.TypeAlias`)]`, found
`ndarray[tuple[int, int, int], dtype[Unknown]]`
> + colour/plotting/temperature.py:513:13: error[invalid-argument-type]
Argument to bound method `text` is incorrect: Expected `int | float`,
found `ndarray[@Todo(Support for `typing.TypeAlias`), dtype[Unknown]]`
> + colour/plotting/temperature.py:514:13: error[invalid-argument-type]
Argument to bound method `text` is incorrect: Expected `int | float`,
found `ndarray[@Todo(Support for `typing.TypeAlias`), dtype[Unknown]]`
> - Found 480 diagnostics
> + Found 488 diagnostics

Most of them are correct except for the last two diagnostics which I'm
not sure
what's happening, it's trying to index into an `np.ndarray` type (which
is
inferred correctly) but I think it might be picking up an incorrect
overload
for the `__getitem__` method.

Scipy's diagnostics also requires support for type alises to pick the
correct overload.

</p>
</details>
2025-08-20 09:39:05 +05:30
Eric Mark Martin 33030b34cd
[ty] linear variance inference for PEP-695 type parameters (#18713)
## Summary

Implement linear-time variance inference for type variables
(https://github.com/astral-sh/ty/issues/488).

Inspired by Martin Huschenbett's [PyCon 2025
Talk](https://www.youtube.com/watch?v=7uixlNTOY4s&t=9705s).

## Test Plan

update tests, add new tests, including for mutually recursive classes

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-19 17:54:09 -07:00
Alex Waygood 656fc335f2
[ty] Strict validation of protocol members (#17750) 2025-08-19 22:45:41 +00:00
Dan Parizher e0f4cec7a1
[`pyupgrade`] Handle nested `Optional`s (`UP045`) (#19770)
## Summary

Fixes #19746

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-19 18:12:15 -04:00
Alex Waygood 662d18bd05
[ty] Add precise inference for unpacking a TypeVar if the TypeVar has an upper bound with a precise tuple spec (#19985) 2025-08-19 22:11:30 +01:00
Aria Desires c82e255ca8
[ty] Fix namespace packages that behave like partial stubs (#19994)
In implementing partial stubs I had observed that this continue in the
namespace package code seemed erroneous since the same continue for
partial stubs didn't work. Unfortunately I wasn't confident enough to
push on that hunch. Fortunately I remembered that hunch to make this an
easy fix.

The issue with the continue is that it bails out of the current
search-path without testing any .py files. This breaks when for example
`google` and `google-stubs`/`types-google` are both in the same
site-packages dir -- failing to find a module in `types-google` has us
completely skip over `google`!

Fixes https://github.com/astral-sh/ty/issues/520
2025-08-19 16:34:39 -04:00
Eric Jolibois 58efd19f11
[ty] apply `KW_ONLY` sentinel only to local fields (#19986)
fix https://github.com/astral-sh/ty/issues/1047

## Summary

This PR fixes how `KW_ONLY` is applied in dataclasses. Previously, the
sentinel leaked into subclasses and incorrectly marked their fields as
keyword-only; now it only affects fields declared in the same class.

```py
from dataclasses import dataclass, KW_ONLY

@dataclass
class D:
    x: int
    _: KW_ONLY
    y: str

@dataclass
class E(D):
    z: bytes

# This should work: x=1 (positional), z=b"foo" (positional), y="foo" (keyword-only)
E(1, b"foo", y="foo")

reveal_type(E.__init__)  # revealed: (self: E, x: int, z: bytes, *, y: str) -> None
```

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

## Test Plan

<!-- How was it tested? -->
mdtests
2025-08-19 11:01:35 -07:00
Aria Desires c6dcfe36d0
[ty] introduce multiline pretty printer (#19979)
Requires some iteration, but this includes the most tedious part --
threading a new concept of DisplaySettings through every type display
impl. Currently it only holds a boolean for multiline, but in the future
it could also take other things like "render to markdown" or "here's
your base indent if you make a newline".

For types which have exposed display functions I've left the old
signature as a compatibility polyfill to avoid having to audit
everywhere that prints types right off the bat (notably I originally
tried doing multiline functions unconditionally and a ton of things
churned that clearly weren't ready for multi-line (diagnostics).

The only real use of this API in this PR is to multiline render function
types in hovers, which is the highest impact (see snapshot changes).

Fixes https://github.com/astral-sh/ty/issues/1000
2025-08-19 17:31:44 +00:00
Avasam 59b078b1bf
Update outdated links to https://typing.python.org/en/latest/source/stubs.html (#19992) 2025-08-19 18:12:08 +01:00
Andrew Gallant 5e943d3539 [ty] Ask the LSP client to watch all project search paths
This change rejiggers how we register globs for file watching with the
LSP client. Previously, we registered a few globs like `**/*.py`,
`**/pyproject.toml` and more. There were two problems with this
approach.

Firstly, it only watches files within the project root. Search paths may
be outside the project root. Such as virtualenv directory.

Secondly, there is variation on how tools interact with virtual
environments. In the case of uv, depending on its link mode, we might
not get any file change notifications after running `uv add foo` or
`uv remove foo`.

To remedy this, we instead just list for file change notifications on
all files for all search paths. This simplifies the globs we use, but
does potentially increase the number of notifications we'll get.
However, given the somewhat simplistic interface supported by the LSP
protocol, I think this is unavoidable (unless we used our own file
watcher, which has its own considerably downsides). Moreover, this is
seemingly consistent with how `ty check --watch` works.

This also required moving file watcher registration to *after*
workspaces are initialized, or else we don't know what the right search
paths are.

This change is in service of #19883, which in order for cache
invalidation to work right, the LSP client needs to send notifications
whenever a dependency is added or removed. This change should make that
possible.

I tried this patch with #19883 in addition to my work to activate Salsa
caching, and everything seems to work as I'd expect. That is,
completions no longer show stale results after a dependency is added or
removed.
2025-08-19 10:57:07 -04:00
Alex Waygood 600245478c
[ty] Look for `site-packages` directories in `<sys.prefix>/lib64/` as well as `<sys.prefix>/lib/` on non-Windows systems (#19978) 2025-08-19 11:53:06 +00:00
Alex Waygood e5c091b850
[ty] Fix protocol interface inference for stub protocols and subprotocols (#19950) 2025-08-19 10:31:11 +00:00
David Peter 10301f6190
[ty] Enable virtual terminal on Windows (#19984)
## Summary

Should hopefully fix https://github.com/astral-sh/ty/issues/1045
2025-08-19 09:13:03 +00:00
Alex Waygood 4242905b36
[ty] Detect `NamedTuple` classes where fields without default values follow fields with default values (#19945) 2025-08-19 08:56:08 +00:00
Aria Desires c20d906503
[ty] improve goto/hover for definitions (#19976)
By computing the actual Definition for, well, definitions, we unlock a
bunch of richer machinery in the goto/hover subsystems for free.

Fixes https://github.com/astral-sh/ty/issues/1001
Fixes https://github.com/astral-sh/ty/issues/1004
2025-08-18 21:42:53 -04:00
Carl Meyer a04375173c
[ty] fix unpacking a type alias with detailed tuple spec (#19981)
## Summary

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

We special-case iteration of certain types because they may have a more
detailed tuple-spec. Now that type aliases are a distinct type variant,
we need to handle them as well.

I don't love that `Type::TypeAlias` means we have to remember to add a
case for it basically anywhere we are special-casing a certain kind of
type, but at the moment I don't have a better plan. It's another
argument for avoiding fallback cases in `Type` matches, which we usually
prefer; I've updated this match statement to be comprehensive.

## Test Plan

Added mdtest.
2025-08-18 17:54:05 -07:00
Alex Waygood e6dcdd29f2
[ty] Add a Todo-type branch for `type[P]` where `P` is a protocol class (#19947) 2025-08-18 20:38:19 +00:00
Matthew Mckee 24f6d2dc13
[ty] Infer the correct type of Enum `__eq__` and `__ne__` comparisions (#19666)
## Summary

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

## Test Plan

Update `enums.md`

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-18 19:45:44 +02:00
Alex Waygood 3314cf90ed
[ty] Add more regression tests for `tuple` (#19974) 2025-08-18 18:30:05 +01:00
Aria Desires 0cb1abc1fc
[ty] Implement partial stubs (#19931)
Fixes https://github.com/astral-sh/ty/issues/184
2025-08-18 13:14:13 -04:00
Alex Waygood fbf24be8ae
[ty] Detect illegal multiple inheritance with `NamedTuple` (#19943) 2025-08-18 12:03:01 +00:00
Micha Reiser 5e4fa9e442
[ty] Speedup tracing checks (#19965) 2025-08-18 12:56:06 +02:00
Micha Reiser 67529edad6
[ty] Short-circuit inlayhints request if disabled in settings (#19963) 2025-08-18 10:35:40 +00:00
Alex Waygood 4ac2b2c222
[ty] Have `SemanticIndex::place_table()` and `SemanticIndex::use_def_map` return references (#19944) 2025-08-18 11:30:52 +01:00
Micha Reiser 7d8f7c20da
[ty] Log server version at info level (#19961) 2025-08-18 07:16:53 +00:00
Alex Waygood ec3163781c
[ty] Remove unused code (#19949) 2025-08-17 18:54:24 +01:00
Douglas Creager b892e4548e
[ty] Track when type variables are inferable or not (#19786)
`Type::TypeVar` now distinguishes whether the typevar in question is
inferable or not.

A typevar is _not inferable_ inside the body of the generic class or
function that binds it:

```py
def f[T](t: T) -> T:
    return t
```

The infered type of `t` in the function body is `TypeVar(T,
NotInferable)`. This represents how e.g. assignability checks need to be
valid for all possible specializations of the typevar. Most of the
existing assignability/etc logic only applies to non-inferable typevars.

Outside of the function body, the typevar is _inferable_:

```py
f(4)
```

Here, the parameter type of `f` is `TypeVar(T, Inferable)`. This
represents how e.g. assignability doesn't need to hold for _all_
specializations; instead, we need to find the constraints under which
this specific assignability check holds.

This is in support of starting to perform specialization inference _as
part of_ performing the assignability check at the call site.

In the [[POPL2015][]] paper, this concept is called _monomorphic_ /
_polymorphic_, but I thought _non-inferable_ / _inferable_ would be
clearer for us.

Depends on #19784 

[POPL2015]: https://doi.org/10.1145/2676726.2676991

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-16 18:25:03 -04:00
Alex Waygood 9ac39cee98
[ty] Ban protocols from inheriting from non-protocol generic classes (#19941) 2025-08-16 19:38:43 +01:00
Alex Waygood f4d8826428
[ty] Fix error message for invalidly providing type arguments to `NamedTuple` when it occurs in a type expression (#19940) 2025-08-16 17:45:15 +00:00
Micha Reiser 527a690a73
[ty] Fix example in environment docs (#19937) 2025-08-16 14:37:28 +00:00
Dan Parizher f0e9c1d8f9
[`isort`] Handle multiple continuation lines after module docstring (`I002`) (#19818)
## Summary

Fixes #19815

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-08-15 17:17:50 -04:00
Frazer McLean 2e1d6623cd
[`flake8-simplify`] Implement fix for `maxsplit` without separator (`SIM905`) (#19851)
**Stacked on top of #19849; diff will include that PR until it is
merged.**

---

<!--
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 part of #19849, I noticed this fix could be implemented.

## Test Plan

Tests added based on CPython behaviour.
2025-08-15 15:18:06 -04:00
Dan Parizher 2dc2f68b0f
[`pycodestyle`] Make `E731` fix unsafe instead of display-only for class assignments (#19700)
## Summary

Fixes #19650

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-15 19:09:55 +00:00
Alex Waygood 26d6c3831f
[ty] Represent `NamedTuple` as an opaque special form, not a class (#19915) 2025-08-15 18:20:14 +01:00
Alex Waygood 9ced219ffc
[ty] Remove incorrect type narrowing for `if type(x) is C[int]` (#19926) 2025-08-15 17:52:14 +01:00
Alex Waygood 6de84ed56e
Add `else`-branch narrowing for `if type(a) is A` when `A` is `@final` (#19925) 2025-08-15 14:52:30 +01:00
github-actions[bot] bd4506aac5
[ty] Sync vendored typeshed stubs (#19923)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 18:09:35 -07:00
Shunsuke Shibayama 0e5577ab56
[ty] fix lazy snapshot sweeping in nested scopes (#19908)
## Summary

This PR closes astral-sh/ty#955.

## Test Plan

New test cases in `narrowing/conditionals/nested.md`.
2025-08-14 17:52:52 -07:00
Andrii Turov 957320c0f1
[ty] Add diagnostics for invalid `await` expressions (#19711)
## Summary

This PR adds a new lint, `invalid-await`, for all sorts of reasons why
an object may not be `await`able, as discussed in astral-sh/ty#919.
Precisely, `__await__` is guarded against being missing, possibly
unbound, or improperly defined (expects additional arguments or doesn't
return an iterator).

Of course, diagnostics need to be fine-tuned. If `__await__` cannot be
called with no extra arguments, it indicates an error (or a quirk?) in
the method signature, not at the call site. Without any doubt, such an
object is not `Awaitable`, but I feel like talking about arguments for
an *implicit* call is a bit leaky.
I didn't reference any actual diagnostic messages in the lint
definition, because I want to hear feedback first.

Also, there's no mention of the actual required method signature for
`__await__` anywhere in the docs. The only reference I had is the
`typing` stub. I basically ended up linking `[Awaitable]` to ["must
implement
`__await__`"](https://docs.python.org/3/library/collections.abc.html#collections.abc.Awaitable),
which is insufficient on its own.

## Test Plan

The following code was tested:
```python
import asyncio
import typing


class Awaitable:
    def __await__(self) -> typing.Generator[typing.Any, None, int]:
        yield None
        return 5


class NoDunderMethod:
    pass


class InvalidAwaitArgs:
    def __await__(self, value: int) -> int:
        return value


class InvalidAwaitReturn:
    def __await__(self) -> int:
        return 5


class InvalidAwaitReturnImplicit:
    def __await__(self):
        pass


async def main() -> None:
    result = await Awaitable()  # valid
    result = await NoDunderMethod()  # `__await__` is missing
    result = await InvalidAwaitReturn()  # `__await__` returns `int`, which is not a valid iterator 
    result = await InvalidAwaitArgs()  # `__await__` expects additional arguments and cannot be called implicitly
    result = await InvalidAwaitReturnImplicit()  # `__await__` returns `Unknown`, which is not a valid iterator


asyncio.run(main())
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-14 14:38:33 -07:00
Alex Waygood f6093452ed
[ty] Synthesize read-only properties for all declared members on `NamedTuple` classes (#19899) 2025-08-14 21:25:45 +00:00
Alex Waygood 82350a398e
[ty] Remove use of `ClassBase::try_from_type` from `super()` machinery (#19902) 2025-08-14 22:14:31 +01:00
Micha Reiser ce938fe205
[ty] Speedup project file discovery (#19913) 2025-08-14 19:38:39 +01:00
Brent Westbrook 7f8f1ab2c1
[`pyflakes`] Add secondary annotation showing previous definition (`F811`) (#19900)
## Summary

This is a second attempt at a first use of a new diagnostic feature
after #19886. I'll blame rustc for this one because it also has a
similar diagnostic:

<img width="735" height="335" alt="image"
src="https://github.com/user-attachments/assets/572fe1c3-1742-4ce4-b575-1d9196ff0932"
/>

We end up with a very similar diagnostic:

<img width="764" height="401" alt="image"
src="https://github.com/user-attachments/assets/01eaf0c7-2567-467b-a5d8-a27206b2c74c"
/>

## Test Plan

New snapshots and manual tests above
2025-08-14 13:23:43 -04:00
Brent Westbrook ef422460de
Bump 0.12.9 (#19917) 2025-08-14 11:54:44 -04:00
justin dc2e8ab377
[ty] support `kw_only=True` for `dataclass()` and `field()` (#19677)
## Summary
https://github.com/astral-sh/ty/issues/111

adds support for `@dataclass(kw_only=True)`
(https://docs.python.org/3/library/dataclasses.html)

## Test Plan
- new mdtests
- triaged conformance diffs (notes here:
https://diffswarm.dev/d-01k2gknwyq82f6x17zqf3apjxc)
- `mypy_primer` no-op
2025-08-14 08:02:55 -07:00
ffgan 9aaa82d037
Feature/build riscv64 bin (#19819) 2025-08-14 16:11:14 +02:00
Alex Waygood 3288ac2dfb
[ty] Add caching to `CodeGeneratorKind::matches()` (#19912) 2025-08-14 11:54:11 +01:00
Dhruv Manilawala 1167ed61cf
[ty] Rename `functionArgumentNames` to `callArgumentNames` inlay hint setting (#19911)
## Summary

This PR renames `ty.inlayHints.functionArgumentNames` to
`ty.inlayHints.callArgumentNames` which would contain both function
calls and class initialization calls i.e., it represents a generic call
expression.
2025-08-14 14:21:38 +05:30
Dhruv Manilawala 2ee47d87b6
[ty] Default `ty.inlayHints.*` server settings to true (#19910)
## Summary

This PR changes the default of `ty.inlayHints.*` settings to `true`.

I somehow missed this in my initial PR.

This is marked as `internal` because it's not yet released.
2025-08-14 14:12:03 +05:30
Carl Meyer 5a570c8e6d
[ty] fix deferred name loading in PEP695 generic classes/functions (#19888)
## Summary

For PEP 695 generic functions and classes, there is an extra "type
params scope" (a child of the outer scope, and wrapping the body scope)
in which the type parameters are defined; class bases and function
parameter/return annotations are resolved in that type-params scope.

This PR fixes some longstanding bugs in how we resolve name loads from
inside these PEP 695 type parameter scopes, and also defers type
inference of PEP 695 typevar bounds/constraints/default, so we can
handle cycles without panicking.

We were previously treating these type-param scopes as lazy nested
scopes, which is wrong. In fact they are eager nested scopes; the class
`C` here inherits `int`, not `str`, and previously we got that wrong:

```py
Base = int

class C[T](Base): ...

Base = str
```

But certain syntactic positions within type param scopes (typevar
bounds/constraints/defaults) are lazy at runtime, and we should use
deferred name resolution for them. This also means they can have cycles;
in order to handle that without panicking in type inference, we need to
actually defer their type inference until after we have constructed the
`TypeVarInstance`.

PEP 695 does specify that typevar bounds and constraints cannot be
generic, and that typevar defaults can only reference prior typevars,
not later ones. This reduces the scope of (valid from the type-system
perspective) cycles somewhat, although cycles are still possible (e.g.
`class C[T: list[C]]`). And this is a type-system-only restriction; from
the runtime perspective an "invalid" case like `class C[T: T]` actually
works fine.

I debated whether to implement the PEP 695 restrictions as a way to
avoid some cycles up-front, but I ended up deciding against that; I'd
rather model the runtime name-resolution semantics accurately, and
implement the PEP 695 restrictions as a separate diagnostic on top.
(This PR doesn't yet implement those diagnostics, thus some `# TODO:
error` in the added tests.)

Introducing the possibility of cyclic typevars made typevar display
potentially stack overflow. For now I've handled this by simply removing
typevar details (bounds/constraints/default) from typevar display. This
impacts display of two kinds of types. If you `reveal_type(T)` on an
unbound `T` you now get just `typing.TypeVar` instead of
`typing.TypeVar("T", ...)` where `...` is the bound/constraints/default.
This matches pyright and mypy; pyrefly uses `type[TypeVar[T]]` which
seems a bit confusing, but does include the name. (We could easily
include the name without cycle issues, if there's a syntax we like for
that.)

It also means that displaying a generic function type like `def f[T:
int](x: T) -> T: ...` now displays as `f[T](x: T) -> T` instead of `f[T:
int](x: T) -> T`. This matches pyright and pyrefly; mypy does include
bound/constraints/defaults of typevars in function/callable type
display. If we wanted to add this, we would either need to thread a
visitor through all the type display code, or add a `decycle` type
transformation that replaced recursive reoccurrence of a type with a
marker.

## Test Plan

Added mdtests and modified existing tests to improve their correctness.

After this PR, there's only a single remaining py-fuzzer seed in the
0-500 range that panics! (Before this PR, there were 10; the fuzzer
likes to generate cyclic PEP 695 syntax.)

## Ecosystem report

It's all just the changes to `TypeVar` display.
2025-08-13 15:51:59 -07:00
Douglas Creager baadb5a78d
[ty] Add some additional type safety to `CycleDetector` (#19903)
This PR adds a type tag to the `CycleDetector` visitor (and its
aliases).

There are some places where we implement e.g. an equivalence check by
making a disjointness check. Both `is_equivalent_to` and
`is_disjoint_from` use a `PairVisitor` to handle cycles, but they should
not use the same visitor. I was finding it tedious to remember when it
was appropriate to pass on a visitor and when not to. This adds a
`PhantomData` type tag to ensure that we can't pass on one method's
visitor to a different method.

For `has_relation` and `apply_type_mapping`, we have an existing type
that we can use as the tag. For the other methods, I've added empty
structs (`Normalized`, `IsDisjointFrom`, `IsEquivalentTo`) to use as
tags.
2025-08-13 17:32:35 -04:00
Roman Kitaev df0648aae0
[`flake8-blind-except`] Fix `BLE001` false-positive on `raise ... from None` (#19755)
## Summary

- Refactored `BLE001` logic for clarity and minor speed-up.
- Improved documentation and comments (previously, `BLE001` docs claimed
it catches bare `except:`s, but it doesn't).
- Fixed a false-positive bug with `from None` cause:

```python
# somefile.py

try:
    pass
except BaseException as e:
    raise e from None
```

### main branch
```
somefile.py:3:8: BLE001 Do not catch blind exception: `BaseException`
  |
1 | try:
2 |     pass
3 | except BaseException as e:
  |        ^^^^^^^^^^^^^ BLE001
4 |     raise e from None
  |

Found 1 error.
```

### this change

```cargo run -p ruff -- check somefile.py --no-cache --select=BLE001```

```
All checks passed!
```

## Test Plan

- Added a test case to cover `raise X from Y` clause
- Added a test case to cover `raise X from None` clause
2025-08-13 13:01:47 -04:00
Aria Desires f0b03c3e86
[ty] resolve docstrings for modules (#19898)
This also reintroduces the `ResolvedDefinition::Module` variant because
reverse-engineering it in several places is a bit confusing. In an ideal
world we wouldn't have `ResolvedDefinition::FileWithRange` as it kinda
kills the ability to do richer analysis, so I want to chip away at its
scope wherever I can (currently it's used to point at asname parts of
import statements when doing `ImportAliasResolution::PreserveAliases`,
and also keyword arguments).

This also makes a kind of odd change to allow a hover to *only* produce
a docstring. This works around an oddity where hovering over a module
name in an import fails to resolve to a `ty` even though hovering over
uses of that imported name *does*.

The two fixed tests reflect the two interesting cases here.
2025-08-13 12:24:01 -04:00
Alex Waygood 9f6146a13d
[ty] Add precise inference for indexing, slicing and unpacking `NamedTuple` instances (#19560)
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-13 15:19:44 +00:00
Brent Westbrook 11d2cb6d56
Add rule code to GitLab description (#19896)
## Summary

Fixes #19881. While I was here, I also made a couple of related tweaks
to the output format. First, we don't need to strip the `SyntaxError: `
prefix anymore since that's not added directly to the diagnostic message
after #19644. Second, we can use `secondary_code_or_id` to fall back on
the lint ID for syntax errors, which changes the `check_name` from
`syntax-error` to `invalid-syntax`. And then the main change requested
in the issue, prepending the `check_name` to the description.

## Test Plan

Existing tests and a new screenshot from GitLab:

<img width="362" height="113" alt="image"
src="https://github.com/user-attachments/assets/97654ad4-a639-4489-8c90-8661c7355097"
/>
2025-08-13 11:19:26 -04:00
Aria Desires d59282ebb5
[ty] render docstrings in hover (#19882)
This PR has several components:

* Introduce a Docstring String wrapper type that has render_plaintext
and render_markdown methods, to force docstring handlers to pick a
rendering format
* Implement [PEP-257](https://peps.python.org/pep-0257/) docstring
trimming for it
* The markdown rendering just renders the content in a plaintext
codeblock for now (followup work)
* Introduce a `DefinitionsOrTargets` type representing the partial
evaluation of `GotoTarget::get_definition_targets` to ideally stop at
getting `ResolvedDefinitions`
* Add `declaration_targets`, `definition_targets`, and `docstring`
methods to `DefinitionsOrTargets` for the 3 usecases we have for this
operation
* `docstring` is of course the key addition here, it uses the same basic
logic that `signature_help` was using: first check the goto-declaration
for docstrings, then check the goto-definition for docstrings.
* Refactor `signature_help` to use the new APIs instead of implementing
it itself
* Not fixed in this PR: an issue I found where `signature_help` will
erroneously cache docs between functions that have the same type (hover
docs don't have this bug)
* A handful of new tests and additions to tests to add docstrings in
various places and see which get caught


Examples of it working with stdlib, third party, and local definitions:
<img width="597" height="120" alt="Screenshot 2025-08-12 at 2 13 55 PM"
src="https://github.com/user-attachments/assets/eae54efd-882e-4b50-b5b4-721595224232"
/>
<img width="598" height="281" alt="Screenshot 2025-08-12 at 2 14 06 PM"
src="https://github.com/user-attachments/assets/5c9740d5-a06b-4c22-9349-da6eb9a9ba5a"
/>
<img width="327" height="180" alt="Screenshot 2025-08-12 at 2 14 18 PM"
src="https://github.com/user-attachments/assets/3b5647b9-2cdd-4c5b-bb7d-da23bff1bcb5"
/>

Notably modules don't work yet (followup work):
<img width="224" height="83" alt="Screenshot 2025-08-12 at 2 14 37 PM"
src="https://github.com/user-attachments/assets/7e9dcb70-a10e-46d9-a85c-9fe52c3b7e7b"
/>

Notably we don't show docs for an item if you hover its actual
definition (followup work, but also, not the most important):
<img width="324" height="69" alt="Screenshot 2025-08-12 at 2 16 54 PM"
src="https://github.com/user-attachments/assets/d4ddcdd8-c3fc-4120-ac93-cefdf57933b4"
/>
2025-08-13 14:59:20 +00:00
Carl Meyer e12747a903
[ty] simplify return type of place_from_declarations (#19884)
## Summary

A [passing
comment](https://github.com/astral-sh/ruff/pull/19711#issuecomment-3169312014)
led me to explore why we didn't report a class attribute as possibly
unbound if it was a method and defined in two different conditional
branches.

I found that the reason was because of our handling of "conflicting
declarations" in `place_from_declarations`. It returned a `Result` which
would be `Err` in case of conflicting declarations.

But we only actually care about conflicting declarations when we are
actually doing type inference on that scope and might emit a diagnostic
about it. And in all cases (including that one), we want to otherwise
proceed with the union of the declared types, as if there was no
conflict.

In several cases we were failing to handle the union of declared types
in the same way as a normal declared type if there was a declared-types
conflict. The `Result` return type made this mistake really easy to
make, as we'd match on e.g. `Ok(Place::Type(...))` and do one thing,
then match on `Err(...)` and do another, even though really both of
those cases should be handled the same.

This PR refactors `place_from_declarations` to instead return a struct
which always represents the declared type we should use in the same way,
as well as carrying the conflicting declared types, if any. This struct
has a method to allow us to explicitly ignore the declared-types
conflict (which is what we want in most cases), as well as a method to
get the declared type and the conflict information, in the case where we
want to emit a diagnostic on the conflict.

## Test Plan

Existing CI; added a test showing that we now understand a
multiply-conditionally-defined method as possibly-unbound.

This does trigger issues on a couple new fuzzer seeds, but the issues
are just new instances of an already-known (and rarely occurring)
problem which I already plan to address in a future PR, so I think it's
OK to land as-is.

I happened to build this initially on top of
https://github.com/astral-sh/ruff/pull/19711, which adds invalid-await
diagnostics, so I also updated some invalid-syntax tests to not await on
an invalid type, since the purpose of those tests is to check the
syntactic location of the `await`, not the validity of the awaited type.
2025-08-13 14:17:08 +00:00
Alex Waygood 5725c4b17f
[ty] Various minor cleanups to tuple internals (#19891) 2025-08-13 13:46:22 +00:00
Alex Waygood 2f3c7ad1fc
[ty] Improve `sys.version_info` special casing (#19894) 2025-08-13 14:39:13 +01:00
Brent Westbrook 79c949f0f7
Don't cache files with diagnostics (#19869)
Summary
--

To take advantage of the new diagnostics, we need to update our caching
model to include all of the information supported by `ruff_db`'s
diagnostic type. Instead of trying to serialize all of this information,
Micha suggested simply not caching files with diagnostics, like we
already do for files with syntax errors. This PR is an attempt at that
approach.

This has the added benefit of trimming down our `Rule` derives since
this was the last place the `FromStr`/`strum_macros::EnumString`
implementation was used, as well as the (de)serialization macros and
`CacheKey`.

Test Plan
--

Existing tests, with their input updated not to include a diagnostic,
plus a new test showing that files with lint diagnostics are not cached.

Benchmarks
--

In addition to tests, we wanted to check that this doesn't degrade
performance too much. I posted part of this new analysis in
https://github.com/astral-sh/ruff/issues/18198#issuecomment-3175048672,
but I'll duplicate it here. In short, there's not much difference
between `main` and this branch for projects with few diagnostics
(`home-assistant`, `airflow`), as expected. The difference for projects
with many diagnostics (`cpython`) is quite a bit bigger (~300 ms vs ~220
ms), but most projects that run ruff regularly are likely to have very
few diagnostics, so this may not be a problem practically.

I guess GitHub isn't really rendering this as I intended, but the extra
separator line is meant to separate the benchmarks on `main` (above the
line) from this branch (below the line).

| Command | Mean [ms] | Min [ms] | Max [ms] |

|:--------------------------------------------------------------|----------:|---------:|---------:|
| `ruff check cpython --no-cache --isolated --exit-zero` | 322.0 | 317.5
| 326.2 |
| `ruff check cpython --isolated --exit-zero` | 217.3 | 209.8 | 237.9 |
| `ruff check home-assistant --no-cache --isolated --exit-zero` | 279.5
| 277.0 | 283.6 |
| `ruff check home-assistant --isolated --exit-zero` | 37.2 | 35.7 |
40.6 |
| `ruff check airflow --no-cache --isolated --exit-zero` | 133.1 | 130.4
| 146.4 |
| `ruff check airflow --isolated --exit-zero` | 34.7 | 32.9 | 41.6 |

|:--------------------------------------------------------------|----------:|---------:|---------:|
| `ruff check cpython --no-cache --isolated --exit-zero` | 330.1 | 324.5
| 333.6 |
| `ruff check cpython --isolated --exit-zero` | 309.2 | 306.1 | 314.7 |
| `ruff check home-assistant --no-cache --isolated --exit-zero` | 288.6
| 279.4 | 302.3 |
| `ruff check home-assistant --isolated --exit-zero` | 39.8 | 36.9 |
42.4 |
| `ruff check airflow --no-cache --isolated --exit-zero` | 134.5 | 131.3
| 140.6 |
| `ruff check airflow --isolated --exit-zero` | 39.1 | 37.2 | 44.3 |

I had Claude adapt one of the
[scripts](https://github.com/sharkdp/hyperfine/blob/master/scripts/plot_whisker.py)
from the hyperfine repo to make this plot, so it's not quite perfect,
but maybe it's still useful. The table is probably more reliable for
close comparisons. I'll put more details about the benchmarks below for
the sake of future reproducibility.

<img width="4472" height="2368" alt="image"
src="https://github.com/user-attachments/assets/1c42d13e-818a-44e7-b34c-247340a936d7"
/>

<details><summary>Benchmark details</summary>
<p>

The versions of each project:
- CPython: 6322edd260e8cad4b09636e05ddfb794a96a0451, the 3.10 branch
from the contributing docs
- `home-assistant`: 5585376b406f099fb29a970b160877b57e5efcb0
- `airflow`: 29a1cb0cfde9d99b1774571688ed86cb60123896

The last two are just the main branches at the time I cloned the repos.

I don't think our Ruff config should be applied since I used
`--isolated`, but these are cloned into my copy of Ruff at
`crates/ruff_linter/resources/test`, and I trimmed the
`./target/release/` prefix from each of the commands, but these are
builds of Ruff in release mode.

And here's the script with the `hyperfine` invocation:

```shell
#!/bin/bash

cargo build --release --bin ruff

# git clone --depth 1 https://github.com/home-assistant/core crates/ruff_linter/resources/test/home-assistant
# git clone --depth 1 https://github.com/apache/airflow crates/ruff_linter/resources/test/airflow

bin=./target/release/ruff
resources=./crates/ruff_linter/resources/test
cpython=$resources/cpython
home_assistant=$resources/home-assistant
airflow=$resources/airflow

base=${1:-bench}

hyperfine --warmup 10 --export-json $base.json --export-markdown $base.md \
		  "$bin check $cpython --no-cache --isolated --exit-zero" \
		  "$bin check $cpython --isolated --exit-zero" \
		  "$bin check $home_assistant --no-cache --isolated --exit-zero" \
		  "$bin check $home_assistant --isolated --exit-zero" \
		  "$bin check $airflow --no-cache --isolated --exit-zero" \
		  "$bin check $airflow --isolated --exit-zero"
```

I ran this once on `main` (`baseline` in the graph, top half of the
table) and once on this branch (`nocache` and bottom of the table).

</p>
</details>
2025-08-12 15:28:44 -04:00
Carl Meyer 13bdba5d28
[ty] support recursive type aliases (#19805)
## Summary

Support recursive type aliases by adding a `Type::TypeAlias` type
variant, which allows referring to a type alias directly as a type
without eagerly unpacking it to its value.

We still unpack type aliases when they are added to intersections and
unions, so that we can simplify the intersection/union appropriately
based on the unpacked value of the type alias.

This introduces new possible recursive types, and so also requires
expanding our usage of recursion-detecting visitors in Type methods. The
use of these visitors is still not fully comprehensive in this PR, and
will require further expansion to support recursion in more kinds of
types (I already have further work on this locally), but I think it may
be better to do this incrementally in multiple PRs.

## Test Plan

Added some recursive type-alias tests and made them pass.
2025-08-12 09:03:10 -07:00
Alex Waygood d76fd103ae
[ty] Remove unsafe `salsa::Update` implementations in `tuple.rs` (#19880) 2025-08-12 15:53:34 +01:00
Matthew Mckee ad28b80f96
[ty] Function argument inlay hints (#19269) 2025-08-12 13:56:54 +00:00
Alex Waygood 3458f365da
[ty] Remove Salsa interning for `TypedDictType` (#19879) 2025-08-12 14:35:26 +01:00
Harutaka Kawamura 94cfdf4b40
Fix `lint.future-annotations` link (#19876) 2025-08-12 14:45:06 +02:00
Alex Waygood 498a04804d
[ty] Reduce memory usage of `TupleSpec` and `TupleType` (#19872) 2025-08-12 12:51:16 +01:00
Ibraheem Ahmed f34b65b7a0
[ty] Track heap usage of salsa structs (#19790)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-12 13:28:44 +02:00
Carl Meyer 28820db1cd
[ty] simplify CycleDetector::visit signature (#19873)
## Summary

After https://github.com/astral-sh/ruff/pull/19871, I realized that now
that we are passing around shared references to `CycleDetector`
visitors, we can now also simplify the `visit` callback signature; we
don't need to smuggle a single visitor reference through it anymore.
This is a pretty minor simplification, and it doesn't really make
anything shorter since I typically used a very short name (`v`) for the
smuggled reference, but I think it reduces cognitive overhead in reading
these `visit` usages; the extra variable would likely be confusing
otherwise for a reader.

## Test Plan

Existing CI.
2025-08-11 17:12:26 -07:00
Carl Meyer ea1aa9ebfe
[ty] use interior mutability in type visitors (#19871)
## Summary

Type visitors are conceptually immutable, they just internally track the
types they've seen (and some maintain a cache of results.) Passing
around mutable visitors everywhere can get us into borrow-checker
trouble in some cases, where we need to recursively pass along the
visitor inside more than one closure with non-disjoint lifetime.

Use interior mutability (via `RefCell` and `Cell`) inside the visitors
instead, to allow us to pass around shared references.

## Test Plan

Existing tests.
2025-08-11 15:42:53 -07:00
Alex Waygood d2fbf2af8f
[ty] Remove `Type::Tuple` (#19669) 2025-08-11 22:03:32 +01:00
Micha Reiser 2abd683376
[ty] Short circuit `ReachabilityConstraints::analyze_single` for dynamic types (#19867) 2025-08-11 21:58:34 +02:00
Douglas Creager dc84645c36
[ty] Use separate Rust types for bound and unbound type variables (#19796)
This PR creates separate Rust types for bound and unbound type
variables, as proposed in https://github.com/astral-sh/ty/issues/926.

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

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-11 15:29:58 -04:00
Alex Waygood f3f4db7104
[ty] Add `static-frame` as a walltime benchmark (#19844) 2025-08-11 15:38:56 +01:00
Matthew Mckee 5063a73d7f
[ty] Update goto range for attribute access to only target the attribute (#19848) 2025-08-11 16:24:14 +02:00
Sneha Prabhu 6bc52f2855
Add AIR301 rule (#17707)
<!--
Thank you for contributing to Ruff! 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?
- Does this pull request include references to any relevant issues?
-->

## Summary

Add "airflow.secrets.cache.SecretCache" →
"airflow.sdk.cache.SecretCache" rule

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

## Test Plan

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

---------

Co-authored-by: Wei Lee <weilee.rx@gmail.com>
2025-08-11 09:14:43 -04:00
Brent Westbrook c433865801
Avoid underflow in default ranges before a BOM (#19839)
Summary
--

This fixes a regression caused by the BOM handling in #19806. Most
diagnostics already account for the BOM in their ranges, but those that
use `TextRange::default` to mean the beginning of the file do not,
causing an underflow in `RenderableAnnotation::new` when subtracting the
BOM-shifted `snippet_start` from the annotation range.

I ran into this when trying to run benchmarks on CPython in preparation
for caching work. The file `cpython/Lib/test/bad_coding2.py` was causing
a crash because it had a default-range `I002` diagnostic, with a BOM.


7cc3f1ebe9/crates/ruff_linter/src/rules/isort/rules/add_required_imports.rs (L122-L126)

The fix here is just to saturate to zero instead of panicking. I
considered adding a `TextRange::saturating_sub` method, but I wasn't
sure it was worth it for this one use. I'm happy to do that if
preferred, though.

Saturating seemed easier than shifting the affected annotations over,
but that could be another solution.

Test Plan
--

A new `ruff_db` test that reproduced the issue and manual testing
against the CPython file mentioned above
2025-08-11 08:52:27 -04:00
Frazer McLean b8a9b1994b
SIM905: Fix handling of U+001C..U+001F whitespace (#19849)
Fixes #19845

## Summary

The linked issue explains it well, Rust and Python do not agree on what
whitespace is for the purposes of `str.split`.
2025-08-11 03:43:04 +00:00
Frazer McLean 4d8ccb6125
RUF064: offer a safe fix for multi-digit zeros (#19847)
Fixes #19010

## Summary

See #19010. `0` was not considered a violation, but `000` was. The
latter will now be fixed to `0o000`.
2025-08-10 20:35:27 +00:00
Brent Westbrook 8230b79829
Clean up unused rendering code in `ruff_linter` (#19832)
## Summary

This is a follow-up to
https://github.com/astral-sh/ruff/pull/19415#discussion_r2263456740 to
remove some unused code. As Micha noticed,
`GroupedEmitter::with_show_source` was only used in local unit tests[^1]
and was safe to remove. This allowed deleting `MessageCodeFrame` and a
lot more helper code previously shared with the `full` output format.

I also moved some other code from `text.rs` and `message/mod.rs` into
`grouped.rs` that is now only used for the `grouped` format. With a
little refactoring of the `concise` rendering logic in `ruff_db`, we
could probably remove `RuleCodeAndBody` too. The only difference I see
from the `concise` output is whether we print the filename next to the
row and column or not:

```shell
> ruff check --output-format concise
try.py:1:8: F401 [*] `math` imported but unused
> ruff check --output-format grouped
try.py:
  1:8 F401 [*] `math` imported but unused
```

But I didn't try to do that here.

## Test Plan

Existing tests, with the source code no longer displayed. I also deleted
one test, as it was now a duplicate of the `default` test.

[^1]: "Local unit tests" as opposed to all of our linter snapshot tests,
as is the case for `TextEmitter::with_show_fix_diff`. We also want to
expose that to users eventually
(https://github.com/astral-sh/ruff/issues/7352), which I don't believe
is the case for the `grouped` format.
2025-08-09 14:20:48 -04:00
Alex Waygood 5a116e48c3
[ty] Add Salsa caching to `TupleType::to_class_type` (#19840) 2025-08-09 09:29:26 +01:00
Douglas Creager 3a542a80f6
[ty] Handle cycles when finding implicit attributes (#19833)
The [minimal
reproduction](https://gist.github.com/dcreager/fc53c59b30d7ce71d478dcb2c1c56444)
of https://github.com/astral-sh/ty/issues/948 is an example of a class
with implicit attributes whose types end up depending on themselves. Our
existing cycle detection for `infer_expression_types` is usually enough
to handle this situation correctly, but when there are very many of
these implicit attributes, we get a combinatorial explosion of running
time and memory usage.

Adding a separate cycle handler for `ClassLiteral::implicit_attribute`
lets us catch and recover from this situation earlier.

Closes https://github.com/astral-sh/ty/issues/948
2025-08-08 17:01:17 -04:00
Aria Desires 4be6fc0979
[ty] fix goto-definition on imports (#19834)
The stub mapper wasn't being passed into this codepath. It is now being
used. A previously messed up test result I intentionally checked in was
subsequently fixed.
2025-08-08 16:46:28 -04:00
Aria Desires 7cc3f1ebe9
[ty] Implement stdlib stub mapping (#19529)
by using essentially the same logic for system site-packages, on the
assumption that system site-packages are always a subdir of the stdlib
we were looking for.
2025-08-08 15:52:15 -04:00
Dan Parizher 0ec4801b0d
[`flake8-comprehensions`] Fix false positive for `C420` with attribute, subscript, or slice assignment targets (#19513)
## Summary

Fixes #19511
2025-08-08 15:02:30 -04:00
Eric Jolibois 0095ff4c1a
[ty] Implement module-level `__getattr__` support (#19791)
fix https://github.com/astral-sh/ty/issues/943

## Summary

Add module-level `__getattr__` support for ty's type checker, fixing
issue https://github.com/astral-sh/ty/issues/943.
Module-level `__getattr__` functions ([PEP
562](https://peps.python.org/pep-0562/)) are now respected when
resolving dynamic attributes, matching the behavior of mypy and pyright.

## Implementation

Thanks @sharkdp for the guidance in
https://github.com/astral-sh/ty/issues/943#issuecomment-3157566579
- Adds module-specific `__getattr__` resolution in
`ModuleLiteral.static_member()`
- Maintains proper attribute precedence: explicit attributes >
submodules > `__getattr__`

## Test Plan
- New mdtest covering basic functionality, type annotations, attribute
precedence, and edge cases
(run ```cargo nextest run -p ty_python_semantic
mdtest__import_module_getattr```)
- All new tests pass, verifying `__getattr__` is called correctly and
returns proper types
  - Existing test suite passes, ensuring no regressions introduced
2025-08-08 10:39:37 -07:00
Brent Westbrook 44755e6e86
Move full diagnostic rendering to `ruff_db` (#19415)
## Summary

This PR switches the `full` output format in Ruff over to use the
rendering code
in `ruff_db`. As proposed in the design doc, this involves a lot of
changes to the snapshot output.

I also had to comment out this assertion with a TODO to replace it after
https://github.com/astral-sh/ruff/issues/19688 because many of Ruff's
"file-level" annotations aren't actually file-level. They just happen to
occur at the start of the file, especially in tests with very short
snippets.


529d81daca/crates/ruff_annotate_snippets/src/renderer/display_list.rs (L1204-L1208)

I broke up the snapshot commits at the end into several blocks, but I
don't think it's enough to help with review. The first few (notebooks,
syntax errors, and test rules) are small enough to look at, but I
couldn't really think of other categories beyond that. I'm happy to
break those up or pick out specific examples beyond what I have below,
if that would help.

The minimal code changes are in this
[range](abd28f1e77),
with the snapshot commits following. Moving the `FullRenderer` and
updating the `EmitterFlags` aren't strictly necessary either. I even
dropped the renderer commit this morning but figured it made sense to
keep it since we have the `full` module for tests. I don't feel strongly
either way.

## Test Plan

I did actually click through all 1700 snapshots individually instead of
accepting them all at once, although I moved through them quickly. There
are a
few main categories:

### Lint diagnostics

```diff
-unused.py:8:19: F401 [*] `pathlib` imported but unused
+F401 [*] `pathlib` imported but unused
+  --> unused.py:8:19
    |
  7 | # Unused, _not_ marked as required (due to the alias).
  8 | import pathlib as non_alias
-   |                   ^^^^^^^^^ F401
+   |                   ^^^^^^^^^
  9 |
 10 | # Unused, marked as required.
    |
-   = help: Remove unused import: `pathlib`
+help: Remove unused import: `pathlib`
```

- The filename and line numbers are moved to the second line
- The second noqa code next to the underline is removed

### Syntax errors

These are much like the above.

```diff
-    -:1:16: invalid-syntax: Expected one or more symbol names after import
+    invalid-syntax: Expected one or more symbol names after import
+     --> -:1:16
       |
     1 | from foo import
       |                ^
```

One thing I noticed while reviewing some of these, but I don't think is
strictly syntax-error-related, is that some of the new diagnostics have
a little less context after the error. I don't think this is a problem,
but it's one small discrepancy I hadn't noticed before. Here's a minor
example:

```diff
-syntax_errors.py:1:15: invalid-syntax: Expected one or more symbol names after import
+invalid-syntax: Expected one or more symbol names after import
+ --> syntax_errors.py:1:15
   |
 1 | from os import
   |               ^
 2 |
 3 | if call(foo
-4 |     def bar():
   |
```

And one of the biggest examples:

```diff
-E30_syntax_error.py:18:11: invalid-syntax: Expected ')', found newline
+invalid-syntax: Expected ')', found newline
+  --> E30_syntax_error.py:18:11
    |
 16 |         pass
 17 |
 18 | foo = Foo(
    |           ^
-19 |
-20 |
-21 | def top(
    |
```

Similarly, a few of the lint diagnostics showed that the cut indicator
calculation for overly long lines is also slightly different, but I
think that's okay too.

### Full-file diagnostics

```diff
-comment.py:1:1: I002 [*] Missing required import: `from __future__ import annotations`
+I002 [*] Missing required import: `from __future__ import annotations`
+--> comment.py:1:1
+help: Insert required import: `from __future__ import annotations`
+
```

As noted above, these will be much more rare after #19688 too. This case
isn't a true full-file diagnostic and will render a snippet in the
future, but you can see that we're now rendering the help message that
would have been discarded before. In contrast, this is a true full-file
diagnostic and should still look like this after #19688:

```diff
-__init__.py:1:1: A005 Module `logging` shadows a Python standard-library module
+A005 Module `logging` shadows a Python standard-library module
+--> __init__.py:1:1
```

### Jupyter notebooks

There's nothing particularly different about these, just showing off the
cell index again.

```diff
-    Jupyter.ipynb:cell 3:1:7: F821 Undefined name `x`
+    F821 Undefined name `x`
+     --> Jupyter.ipynb:cell 3:1:7
       |
     1 | print(x)
-      |       ^ F821
+      |       ^
       |
```
2025-08-08 12:56:23 -04:00
Alex Waygood 8489816edc
[ty] Improve ability to solve TypeVars when they appear in unions (#19829) 2025-08-08 17:50:37 +01:00
Brent Westbrook 8199154d54
[ty] Fix a few more diagnostic differences from Ruff (#19806)
## Summary

Fixes the remaining range reporting differences between the `ruff_db`
diagnostic rendering and Ruff's existing rendering, as noted in
https://github.com/astral-sh/ruff/pull/19415#issuecomment-3160525595.

This PR is structured as a series of three pairs. The first commit in
each pair adds a test showing the previous behavior, followed by a fix
and the updated snapshot. It's quite a small PR, but that might be
helpful just for the contrast.

You can also look at [this
range](052e656c6c..c3ea51030d)
of commits from #19415 to see the impact on real Ruff diagnostics. I
spun these commits out of that PR.

## Test Plan

New `ruff_db` tests
2025-08-08 11:31:19 -04:00
ember91 50e1ecc086
[`pylint`] Use lowercase hex characters to match the formatter (`PLE2513`) (#19808)
PLE2513 --fix changes ESC and SUB to uppercase hexadecimal values such
as \x1B while the formatter changes them to lowercase \x1b

<!--
Thank you for contributing to Ruff/ty! To help us out with reviewing,
please consider the following:

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

## Summary

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

## Test Plan

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

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-08-08 12:25:11 +00:00
Micha Reiser fd35435281
[ty] Improve performance of subtyping and assignability checks for protocols (#19824) 2025-08-08 13:05:12 +02:00
Dhruv Manilawala fc72ff4a94
[ty] Send a single request for registrations/unregistrations (#19822)
## Summary

This is a small refactor to update the server to send a single request
to perform registrations and unregistrations of dynamic capabilities.

## Test Plan

Existing E2E test cases pass, add a new test case to verify multiple
registrations.
2025-08-08 08:42:48 +00:00
Jack O'Connor 827456f977 [ty] more cases for the class body global fallback 2025-08-07 17:30:27 -07:00
Shunsuke Shibayama 462adfd0e6
[ty] fix incorrect member narrowing (#19802)
## Summary

Reported in:
https://github.com/astral-sh/ruff/pull/19795#issuecomment-3161981945

If a root expression is reassigned, narrowing on the member should be
invalidated, but there was an oversight in the current implementation.

This PR fixes that, and also removes some unnecessary handling.

## Test Plan

New tests cases in `narrow/conditionals/nested.md`.
2025-08-07 16:04:07 -07:00
Dylan f51a228f04
Bump 0.12.8 (#19813) 2025-08-07 13:52:16 -05:00
Andrew Gallant d5e1b7983e
[ty] Fix static assertion size check (#19814)
A `Segment` has a `Box` in it, which has a platform dependent size.
Restrict the check to only 64-bit targets.
2025-08-07 13:38:16 -05:00
Micha Reiser 7dfde3b929
Update Rust toolchain to 1.89 (#19807) 2025-08-07 18:21:50 +02:00
Dhruv Manilawala b22586fa0e
[ty] Add `ty.inlayHints.variableTypes` server option (#19780)
## Summary

This PR adds a new `ty.inlayHints.variableTypes` server setting to
configure ty to include / exclude inlay hints at variable position.

Currently, we only support inlay hints at this position so this option
basically translates to enabling / disabling inlay hints for now :)

The VS Code extension PR is
https://github.com/astral-sh/ty-vscode/pull/112.

closes: astral-sh/ty#472

## Test Plan

Add E2E tests.
2025-08-07 19:16:51 +05:30
Alex Waygood c401a6d86e
[ty] Add failing tests for tuple subclasses (#19803) 2025-08-07 13:11:15 +00:00
Dhruv Manilawala 7b6abfb030
[ty] Add `ty.experimental.rename` server setting (#19800)
## Summary

This PR is a follow-up from https://github.com/astral-sh/ruff/pull/19551
and adds a new `ty.experimental.rename` setting to conditionally
register for the rename capability. The complementary PR in ty VS Code
extension is https://github.com/astral-sh/ty-vscode/pull/111.

This is done using dynamic registration after the settings have been
resolved. The experimental group is part of the global settings because
they're applied for all workspaces that are managed by the client.

## Test Plan

Add E2E tests.

In VS Code, with the following setting:
```json
{
	"ty.experimental.rename": "true",
	"python.languageServer": "None"
}
```

I get the relevant log entry:
```
2025-08-07 16:05:40.598709000 DEBUG client_response{id=3 method="client/registerCapability"}: Registered rename capability
```

And, I'm able to rename a symbol. Once I set it to `false`, then I can
see this log entry:

```
2025-08-07 16:08:39.027876000 DEBUG Rename capability is disabled in the client settings
```

And, I don't see the "Rename Symbol" open in the VS Code dropdown.


https://github.com/user-attachments/assets/501659df-ba96-4252-bf51-6f22acb4920b
2025-08-07 12:54:58 +00:00
UnboundVariable b005cdb7ff
[ty] Implemented support for "rename" language server feature (#19551)
This PR adds support for the "rename" language server feature. It builds
upon existing functionality used for "go to references".

The "rename" feature involves two language server requests. The first is
a "prepare rename" request that determines whether renaming should be
possible for the identifier at the current offset. The second is a
"rename" request that returns a list of file ranges where the rename
should be applied.

Care must be taken when attempting to rename symbols that span files,
especially if the symbols are defined in files that are not part of the
project. We don't want to modify code in the user's Python environment
or in the vendored stub files.

I found a few bugs in the "go to references" feature when implementing
"rename", and those bug fixes are included in this PR.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-08-07 15:58:18 +05:30
Micha Reiser b96aa4605b
[ty] Reduce size of member table (#19572) 2025-08-07 11:16:04 +02:00
Dhruv Manilawala cc97579c3b
[ty] Move server capabilities creation (#19798) 2025-08-07 04:28:08 +00:00
Matthew Mckee ef1802b94f
[ty] Repurpose `FunctionType.into_bound_method_type` to return `BoundMethodType` (#19793)
## Summary

As per our naming scheme (at least for callable types) this should
return a `BoundMethodType`, or be renamed, but it makes more sense to
change the return type.

I also ensure `ClassType.into_callable` returns a `Type::Callable` in
the changed branch.

Ideally we could return a `CallableType` from these `into_callable`
functions (and rename to `into_callable_type` but because of unions we
cannot do this.
2025-08-06 15:24:59 -07:00
David Peter 98df62db79
[ty] Validate writes to `TypedDict` keys (#19782)
## Summary

Validates writes to `TypedDict` keys, for example:

```py
class Person(TypedDict):
    name: str
    age: int | None


def f(person: Person):
    person["naem"] = "Alice"  # error: [invalid-key]

    person["age"] = "42"  # error: [invalid-assignment]
```

The new specialized `invalid-assignment` diagnostic looks like this:

<img width="1160" height="279" alt="image"
src="https://github.com/user-attachments/assets/51259455-3501-4829-a84e-df26ff90bd89"
/>

## Ecosystem analysis

As far as I can tell, all true positives!

There are some extremely long diagnostic messages. We should truncate
our display of overload sets somehow.

## Test Plan

New Markdown tests
2025-08-06 15:19:13 -07:00
Matthew Mckee 65b39f2ca9
[ty] Add support for using the test command emitted when a mdtest fails (#19794)
## Summary

When seeing a failed test like 

```bash
is_subtype_of.md - Subtype relation - Callable - Class literals - Classes with `__new_… (1e9782853227c019)

  crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md:1810 unexpected error: [unresolved-reference] "Name `Aa` used when not defined"

To rerun this specific test, set the environment variable: MDTEST_TEST_FILTER='is_subtype_of.md - Subtype relation - Callable - Class literals - Classes with `__new_… (1e9782853227c019)'
MDTEST_TEST_FILTER='is_subtype_of.md - Subtype relation - Callable - Class literals - Classes with `__new_… (1e9782853227c019)' cargo test -p ty_python_semantic --test mdtest -- mdtest__type_properties_is_subtype_of
```

running the following now works

```bash
MDTEST_TEST_FILTER='is_subtype_of.md - Subtype relation - Callable - Class literals - Classes with `__new_… (1e9782853227c019)' cargo test -p ty_python_semantic --test mdtest -- mdtest__type_properties_is_subtype_of
```


## Test Plan

Do we have tests for the test runner? :)
2025-08-06 15:02:10 -07:00
Douglas Creager 585ce12ace
[ty] `typing.Self` is bound by the method, not the class (#19784)
This fixes our logic for binding a legacy typevar with its binding
context. (To recap, a legacy typevar starts out "unbound" when it is
first created, and each time it's used in a generic class or function,
we "bind" it with the corresponding `Definition`.)

We treat `typing.Self` the same as a legacy typevar, and so we apply
this binding logic to it too. Before, we were using the enclosing class
as its binding context. But that's not correct — it's the method where
`typing.Self` is used that binds the typevar. (Each invocation of the
method will find a new specialization of `Self` based on the specific
instance type containing the invoked method.)

This required plumbing through some additional state to the
`in_type_expression` method.

This also revealed that we weren't handling `Self`-typed instance
attributes correctly (but were coincidentally not getting the expected
false positive diagnostics).
2025-08-06 17:26:17 -04:00
Ibraheem Ahmed 21ac16db85
[ty] Avoid overcounting shared memory usage (#19773)
## Summary

Use a global tracker to avoid double counting `Arc` instances.
2025-08-06 15:32:02 -04:00
Dan Parizher 745742e414
[`pylint`] Mark `PLC0207` fixes as unsafe when `*args` unpacking is present (#19679)
## Summary

Fixes #19660
2025-08-06 14:19:49 -04:00
Dhruv Manilawala ec5660d786
[ty] Avoid warning for old settings schema too aggresively (#19787)
## Summary

This PR avoids warning the users too aggressively by checking the
structure of the initialization and workspace options and avoids the
warning if they conform to the old schema.

## Test Plan



https://github.com/user-attachments/assets/9ade9dc4-90cb-4fd4-abd0-4bc4177df3db
2025-08-06 16:16:59 +00:00
David Peter b96929ee19
[ty] Disallow `typing.TypedDict` in type expressions (#19777)
## Summary

Disallow `typing.TypedDict` in type expressions.

Related reference: https://github.com/python/mypy/issues/11030

## Test Plan

New Markdown tests, checked ecosystem and conformance test impact.
2025-08-06 15:58:35 +02:00
Dhruv Manilawala fa711fa40f
[ty] Warn users if server received unknown options (#19779)
## Summary

This PR updates the client settings handling to recognize unknown
options provided by the user and show a warning popup along with a
warning log message.

## Test Plan

Add E2E tests.
2025-08-06 13:11:13 +00:00
Dhruv Manilawala 1f29a04e9a
[ty] Support LSP client settings (#19614)
## Summary

This PR implements support for providing LSP client settings.

The complementary PR in the ty VS Code extension:
astral-sh/ty-vscode#106.

Notes for the previous iteration of this PR is in
https://github.com/astral-sh/ruff/pull/19614#issuecomment-3136477864
(click on "Details").

Specifically, this PR splits the client settings into 3 distinct groups.
Keep in mind that these groups are not visible to the user, they're
merely an implementation detail. The groups are:
1. `GlobalOptions` - these are the options that are global to the
language server and will be the same for all the workspaces that are
handled by the server
2. `WorkspaceOptions` - these are the options that are specific to a
workspace and will be applied only when running any logic for that
workspace
3. `InitializationOptions` - these are the options that can be specified
during initialization

The initialization options are a superset that contains both the global
and workspace options flattened into a 1-dimensional structure. This
means that the user can specify any and all fields present in
`GlobalOptions` and `WorkspaceOptions` in the initialization options in
addition to the fields that are _specific_ to initialization options.

From the current set of available settings, following are only available
during initialization because they are required at that time, are static
during the runtime of the server and changing their values require a
restart to take effect:
- `logLevel`
- `logFile`

And, following are available under `GlobalOptions`:
- `diagnosticMode`

And, following under `WorkspaceOptions`:
- `disableLanguageServices`
- `pythonExtension` (Python environment information that is populated by
the ty VS Code extension)

### `workspace/configuration`

This request allows server to ask the client for configuration to a
specific workspace. But, this is only supported by the client that has
the `workspace.configuration` client capability set to `true`. What to
do for clients that don't support pulling configurations?

In that case, the settings needs to be provided in the initialization
options and updating the values of those settings can only be done by
restarting the server. With the way this is implemented, this means that
if the client does not support pulling workspace configuration then
there's no way to specify settings specific to a workspace. Earlier,
this would've been possible by providing an array of client options with
an additional field which specifies which workspace the options belong
to but that adds complexity and clients that actually do not support
`workspace/configuration` would usually not support multiple workspaces
either.

Now, for the clients that do support this, the server will initiate the
request to get the configuration for all the workspaces at the start of
the server. Once the server receives these options, it will resolve them
for each workspace as follows:
1. Combine the client options sent during initialization with the
options specific to the workspace creating the final client options
that's specific to this workspace
2. Create a global options by combining the global options from (1) for
all workspaces which in turn will also combine the global options sent
during initialization

The global options are resolved into the global settings and are
available on the `Session` which is initialized with the default global
settings. The workspace options are resolved into the workspace settings
and are available on the respective `Workspace`.

The `SessionSnapshot` contains the global settings while the document
snapshot contains the workspace settings. We could add the global
settings to the document snapshot but that's currently not needed.

### Document diagnostic dynamic registration

Currently, the document diagnostic server capability is created based on
the `diagnosticMode` sent during initialization. But, that wouldn't
provide us with the complete picture. This means the server needs to
defer registering the document diagnostic capability at a later point
once the settings have been resolved.

This is done using dynamic registration for clients that support it. For
clients that do not support dynamic registration for document diagnostic
capability, the server advertises itself as always supporting workspace
diagnostics and work done progress token.

This dynamic registration now allows us to change the server capability
for workspace diagnostics based on the resolved `diagnosticMode` value.
In the future, once `workspace/didChangeConfiguration` is supported, we
can avoid the server restart when users have changed any client
settings.

## Test Plan

Add integration tests and recorded videos on the user experience in
various editors:

### VS Code

For VS Code users, the settings experience is unchanged because the
extension defines it's own interface on how the user can specify the
server setting. This means everything is under the `ty.*` namespace as
usual.


https://github.com/user-attachments/assets/c2e5ba5c-7617-406e-a09d-e397ce9c3b93

### Zed

For Zed, the settings experience has changed. Users can specify settings
during initialization:

```json
{
  "lsp": {
    "ty": {
      "initialization_options": {
        "logLevel": "debug",
        "logFile": "~/.cache/ty.log",
        "diagnosticMode": "workspace",
        "disableLanguageServices": true
      }
    },
  }
}
```

Or, can specify the options under the `settings` key:

```json
{
  "lsp": {
    "ty": {
      "settings": {
        "ty": {
          "diagnosticMode": "openFilesOnly",
          "disableLanguageServices": true
        }
      },
      "initialization_options": {
        "logLevel": "debug",
        "logFile": "~/.cache/ty.log"
      }
    },
  }
}
```

The `logLevel` and `logFile` setting still needs to go under the
initialization options because they're required by the server during
initialization.

We can remove the nesting of the settings under the "ty" namespace by
updating the return type of
db9ea0cdfd/src/tychecker.rs (L45-L49)
to be wrapped inside `ty` directly so that users can avoid doing the
double nesting.

There's one issue here which is that if the `diagnosticMode` is
specified in both the initialization option and settings key, then the
resolution is a bit different - if either of them is set to be
`workspace`, then it wins which means that in the following
configuration, the diagnostic mode is `workspace`:

```json
{
  "lsp": {
    "ty": {
      "settings": {
        "ty": {
          "diagnosticMode": "openFilesOnly"
        }
      },
      "initialization_options": {
        "diagnosticMode": "workspace"
      }
    },
  }
}
```

This behavior is mainly a result of combining global options from
various workspace configuration results. Users should not be able to
provide global options in multiple workspaces but that restriction
cannot be done on the server side. The ty VS Code extension restricts
these global settings to only be set in the user settings and not in
workspace settings but we do not control extensions in other editors.


https://github.com/user-attachments/assets/8e2d6c09-18e6-49e5-ab78-6cf942fe1255

### Neovim

Same as in Zed.

### Other

Other editors that do not support `workspace/configuration`, the users
would need to provide the server settings during initialization.
2025-08-06 18:37:21 +05:30
Alex Waygood 529d81daca
[ty] Improve subscript narrowing for "safe mutable classes" (#19781)
## Summary

This PR improves the `is_safe_mutable_class` function in `infer.rs` in
several ways:
- It uses `KnownClass::to_instance()` for all "safe mutable classes".
Previously, we were using `SpecialFormType::instance_fallback()` for
some variants -- I'm not totally sure why. Switching to
`KnownClass::to_instance()` for all "safe mutable classes" fixes a
number of TODOs in the `assignment.md` mdtest suite
- Rather than eagerly calling `.to_instance(db)` on all "safe mutable
classes" every time `is_safe_mutable_class` is called, we now only call
it lazily on each element, allowing us to short-circuit more
effectively.
- I removed the entry entirely for `TypedDict` from the list of "safe
mutable classes", as it's not correct.
`SpecialFormType::TypedDict.instance_fallback(db)` just returns an
instance type representing "any instance of `typing._SpecialForm`",
which I don't think was the intent of this code. No tests fail as a
result of removing this entry, as we already check separately whether an
object is an inhabitant of a `TypedDict` type (and consider that object
safe-mutable if so!).

## Test Plan

mdtests updated
2025-08-06 12:26:25 +01:00
David Peter 4887bdf205
[ty] Infer types for key-based access on TypedDicts (#19763)
## Summary

This PR adds type inference for key-based access on `TypedDict`s and a
new diagnostic for invalid subscript accesses:

```py
class Person(TypedDict):
    name: str
    age: int | None

alice = Person(name="Alice", age=25)

reveal_type(alice["name"])  # revealed: str
reveal_type(alice["age"])  # revealed: int | None

alice["naem"]  # Unknown key "naem" - did you mean "name"?
```

## Test Plan

Updated Markdown tests
2025-08-06 09:36:33 +02:00
Dan Parizher e917d309f1
[`flake8_import_conventions`] Avoid false positives for NFKC-normalized `__debug__` import aliases in ICN001 (#19411)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-06 06:42:51 +00:00
Matthew Mckee 18ad2848e3
Display generic function signature properly (#19544)
## Summary

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

## Test Plan

Update mdtest

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
2025-08-05 16:35:08 -07:00
Brent Westbrook 5bfffe1aa7
[ty] Remap Jupyter notebook cell indices in `ruff_db` (#19698)
## Summary

This PR remaps ranges in Jupyter notebooks from simple `row:column`
indices in the concatenated source code to `cell:row:col` to match
Ruff's output. This is probably not a likely change to land upstream in
`annotate-snippets`, but I didn't see a good way around it.

The remapping logic is taken nearly verbatim from here:


cd6bf1457d/crates/ruff_linter/src/message/text.rs (L212-L222)


## Test Plan

New `full` rendering test for a notebook

I was mainly focused on Ruff, but in local tests this also works for ty:

```
error[invalid-assignment]: Object of type `Literal[1]` is not assignable to `str`
 --> Untitled.ipynb:cell 1:3:1
  |
1 | import math
2 |
3 | x: str = 1
  | ^
  |
info: rule `invalid-assignment` is enabled by default

error[invalid-assignment]: Object of type `Literal[1]` is not assignable to `str`
 --> Untitled.ipynb:cell 2:3:1
  |
1 | import math
2 |
3 | x: str = 1
  | ^
  |
info: rule `invalid-assignment` is enabled by default
```

This isn't a duplicate diagnostic, just an unimaginative example:

```py
# cell 1
import math

x: str = 1
# cell 2
import math

x: str = 1
```
2025-08-05 14:10:35 -04:00
Brent Westbrook b324ae1be3
Hide empty snippets for full-file diagnostics (#19653)
Summary
--

This is the other commit I wanted to spin off from #19415, currently
stacked on #19644.

This PR suppresses blank snippets for empty ranges at the very beginning
of a file, and for empty ranges in non-existent files. Ruff includes
empty ranges for IO errors, for example.


f4e93b6335/crates/ruff_linter/src/message/text.rs (L100-L110)

The diagnostics now look like this (new snapshot test):

```
error[test-diagnostic]: main diagnostic message
--> example.py:1:1                             
```

Instead of [^*]

```
error[test-diagnostic]: main diagnostic message
--> example.py:1:1
 |
 |
```

Test Plan
--

A new `ruff_db` test showing the expected output format

[^*]: This doesn't correspond precisely to the example in the PR because
of some details of the diagnostic builder helper methods in `ruff_db`,
but you can see another example in the current version of the summary in
#19415.
2025-08-05 11:20:31 -04:00
Brent Westbrook 2db4e5dbea
Use fixed hash width for `ty_server` diagnostics (#19766)
Summary
--

Fixes a snapshot test failure I saw in #19653 locally and in Windows CI
by
padding the hex ID to 16 digits to match the regex in
`filter_result_id`.


78e5fe0a51/crates/ty_server/tests/e2e/pull_diagnostics.rs (L380-L384)

Test Plan
--

I applied this to the branch from #19653 locally and saw that the tests
now
pass. I couldn't reproduce this failure directly on `main` or this
branch,
though.
2025-08-05 10:55:17 -04:00
Alex Waygood 4090297a11
[ty] Fix more false positives related to `Generic` or `Protocol` being subscripted with a `ParamSpec` or `TypeVarTuple` (#19764) 2025-08-05 15:45:56 +01:00
Simon Lamon 934fd37d2b
[ty] Diagnostics for async context managers (#19704)
## Summary

Implements diagnostics for async context managers. Fixes
https://github.com/astral-sh/ty/issues/918.

## Test Plan

Mdtests have been added.
2025-08-05 07:41:37 -07:00
Brent Westbrook 78e5fe0a51
Allow hiding the diagnostic severity in `ruff_db` (#19644)
## Summary

This PR is a spin-off from https://github.com/astral-sh/ruff/pull/19415.
It enables replacing the severity and lint name in a ty-style
diagnostic:

```
error[unused-import]: `os` imported but unused
```

with the noqa code and optional fix availability icon for a Ruff
diagnostic:

```
F401 [*] `os` imported but unused
F821 Undefined name `a`
```

or nothing at all for a Ruff syntax error:

```
SyntaxError: Expected one or more symbol names after import
```

Ruff adds the `SyntaxError` prefix to these messages manually.

Initially (d912458), I just passed a `hide_severity` flag through a
bunch of calls to get it into `annotate-snippets`, but after looking at
it again today, I think reusing the `None` severity/level gave a nicer
result. As I note in a lengthy code comment, I think all of this code
should be temporary and reverted when Ruff gets real severities, so
hopefully it's okay if it feels a little hacky.

I think the main visible downside of this approach is that we can't
style the asterisk in the fix availabilty icon in cyan, as in Ruff's
current output. It's part of the message in this PR and any styling gets
overwritten in `annotate-snippets`.

<img width="400" height="342" alt="image"
src="https://github.com/user-attachments/assets/57542ec9-a81c-4a01-91c7-bd6d7ec99f99"
/>

Hmm, I guess reusing `Level::None` also means the `F401` isn't red
anymore. Maybe my initial approach was better after all. In any case,
the rest of the PR should be basically the same, it just depends how we
want to toggle the severity.

## Test Plan

New `ruff_db` tests. These snapshots should be compared to the two tests
just above them (`hide_severity_output` vs `output` and
`hide_severity_syntax_errors` against `syntax_errors`).
2025-08-05 09:56:18 -04:00
Alex Waygood 7dccb6a98c
[ty] Improve effectiveness of `KnownClass` fast paths in `instance.rs` (#19762) 2025-08-05 13:26:14 +01:00
David Peter 948f3f856c
[ty] Fix attribute access on `TypedDict`s (#19758)
## Summary

This PR fixes a few inaccuracies in attribute access on `TypedDict`s. It
also changes the return type of `type(person)` to `type[dict[str,
object]]` if `person: Person` is an inhabitant of a `TypedDict`
`Person`. We still use `type[Person]` as the *meta type* of Person,
however (see reasoning
[here](https://github.com/astral-sh/ruff/pull/19733#discussion_r2253297926)).

## Test Plan

Updated Markdown tests.
2025-08-05 13:59:10 +02:00
Alex Waygood 3af0b31de3
[ty] Speedup the `known_class_doesnt_fallback_to_unknown_unexpectedly_on_low_python_version` test (#19760) 2025-08-05 11:55:11 +00:00
David Peter 7df7be5c7d
[ty] Keep track of type qualifiers in stub declarations without right-hand side (#19756)
## Summary

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

## Test Plan

Regression test
2025-08-05 12:07:05 +02:00
David Peter 14fbc2b167
[ty] New `Type` variant for `TypedDict` (#19733)
## Summary

This PR adds a new `Type::TypedDict` variant. Before this PR, we treated
`TypedDict`-based types as dynamic Todo-types, and I originally planned
to make this change a no-op. And we do in fact still treat that new
variant similar to a dynamic type when it comes to type properties such
as assignability and subtyping. But then I somehow tricked myself into
implementing some of the things correctly, so here we are. The two main
behavioral changes are: (1) we now also detect generic `TypedDict`s,
which removes a few false positives in the ecosystem, and (2) we now
support *attribute* access (not key-based indexing!) on these types,
i.e. we infer proper types for something like
`MyTypedDict.__required_keys__`. Nothing exciting yet, but gets the
infrastructure into place.

Note that with this PR, the type of (the type) `MyTypedDict` itself is
still represented as a `Type::ClassLiteral` or `Type::GenericAlias` (in
case `MyTypedDict` is generic). Only inhabitants of `MyTypedDict`
(instances of `dict` at runtime) are represented by `Type::TypedDict`.
We may want to revisit this decision in the future, if this turns out to
be too error-prone. Right now, we need to use `.is_typed_dict(db)` in
all the right places to distinguish between actual (generic) classes and
`TypedDict`s. But so far, it seemed unnecessary to add additional `Type`
variants for these as well.

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

## Ecosystem impact

The new diagnostics on `cloud-init` look like true positives to me.

## Test Plan

Updated and new Markdown tests
2025-08-05 11:19:49 +02:00
Shunsuke Shibayama 351121c5c5
[ty] fix incorrect lazy scope narrowing (#19744)
## Summary

This is a follow-up to #19321.

Narrowing constraints introduced in a class scope were not applied even
when they can be applied in lazy nested scopes. This PR fixes so that
they are now applied.
Conversely, there were cases where narrowing constraints were being
applied in places where they should not, so it is also fixed.

## Test Plan

Some TODOs in `narrow/conditionals/nested.md` are now work correctly.
2025-08-04 20:32:08 -07:00
Shunsuke Shibayama 64bcc8db2f
[ty] fix lookup order of class variables before they are defined (#19743)
## Summary

This is a follow-up to #19321.

If we try to access a class variable before it is defined, the variable
is looked up in the global scope, rather than in any enclosing scopes.

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

## Test Plan

New tests in `narrow/conditionals/nested.md`.
2025-08-04 20:21:28 -07:00
Roman Kitaev b0f01ba514
[`flake8-blind-except`] Change `BLE001` to correctly parse exception tuples (#19747)
## Summary

This PR enhances the `BLE001` rule to correctly detect blind exception
handling in tuple exceptions. Previously, the rule only checked single
exception types, but Python allows catching multiple exceptions using
tuples like `except (Exception, ValueError):`.

## Test Plan

It fails the following (whereas the main branch does not):

```bash
cargo run -p ruff -- check somefile.py --no-cache --select=BLE001
```

```python
# somefile.py

try:
    1/0
except (ValueError, Exception) as e:
    print(e)
```

```
somefile.py:3:21: BLE001 Do not catch blind exception: `Exception`
  |
1 | try:
2 |     1/0
3 | except (ValueError, Exception) as e:
  |                     ^^^^^^^^^ BLE001
4 |     print(e)
  |

Found 1 error.
```
2025-08-04 21:12:45 +00:00
Alex Waygood 3a9341f7be
[ty] Remove false positives when subscripting `Generic` or `Protocol` with a `ParamSpec` or `TypeVarTuple` (#19749) 2025-08-04 21:42:46 +01:00
David Peter 739c94f95a
[ty] Support as-patterns in reachability analysis (#19728)
## Summary

Support `as` patterns in reachability analysis:

```py
from typing import assert_never


def f(subject: str | int):
    match subject:
        case int() as x:
            pass
        case str():
            pass
        case _:
            assert_never(subject)  # would previously emit an error
```

Note that we still don't support inferring correct types for the bound
name (`x`).

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

## Test Plan

New Markdown tests
2025-08-04 20:13:50 +02:00
Alex Waygood 41207ec901
[ty] Infer `type[tuple[int, str]]` as the meta-type of `tuple[int, str]` (#19741) 2025-08-04 13:10:47 +00:00
Alex Waygood bc6e8b58ce
[ty] Return `Option<TupleType>` from `infer_tuple_type_expression` (#19735)
## Summary

This PR reduces the virality of some of the `Todo` types in
`infer_tuple_type_expression`. Rather than inferring `Todo`, we instead
infer `tuple[Todo, ...]`. This reflects the fact that whatever the
contents of the slice in a `tuple[]` type expression, we would always
infer some kind of tuple type as the result of the type expression. Any
tuple type should be assignable to `tuple[Todo, ...]`, so this shouldn't
introduce any new false positives; this can be seen in the ecosystem
report.

As a result of the change, we are now able to enforce in the signature
of `Type::infer_tuple_type_expression` that it returns an
`Option<TupleType<'db>>`, which is more strongly typed and expresses
clearly the invariant that a tuple type expression should always be
inferred as a `tuple` type. To enable this, it was necessary to refactor
several `TupleType` constructors in `tuple.rs` so that they return
`Option<TupleType>` rather than `Type`; this means that callers of these
constructor functions are now free to either propagate the
`Option<TupleType<'db>>` or convert it to a `Type<'db>`.

## Test Plan

Mdtests updated.
2025-08-04 13:48:19 +01:00
Micha Reiser e4d6b54a16
[ty] Fix failing test on windows (#19742) 2025-08-04 14:39:36 +02:00
Micha Reiser 17ee2a28ba
[ty] Fix workspace diagnostics being recomputed (#19689) 2025-08-04 13:49:38 +02:00
Leandro Braga de77b29798
[ty] clear the terminal screen in watch mode (#19712)
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-04 13:45:37 +02:00
Micha Reiser f473f6b6e5
[ty] Implement long-polling for workspace diagnsotics (#19670) 2025-08-04 10:26:38 +00:00
Micha Reiser 8289432252
[ty] Always refresh diagnostics after a watched files change (#19697) 2025-08-04 12:19:18 +02:00
Micha Reiser 808c94d509
[ty] Implement streaming for workspace diagnostics (#19657) 2025-08-04 09:34:29 +00:00
Micha Reiser b95d22c08e
Don't flag `pyrefly` pragmas as unused code (`ERA001`) (#19731) 2025-08-04 10:15:37 +02:00
Micha Reiser 6516db7835
[ty] Add progress bar to watch (#19729) 2025-08-04 09:31:13 +02:00
Dan Parizher 1a368b0bf9
[`flake8-simplify`] Fix raw string handling in `SIM905` for embedded quotes (#19591)
## Summary

When splitting triple-quoted, raw strings one has to take care before attempting to make each item have single-quotes.

Fixes #19577

---------

Co-authored-by: dylwil3 <dylwil3@gmail.com>
2025-08-03 11:31:28 -05:00
Jérémy Scanvic 134435415e
Change 'associative' to 'commutative' in docs describing union (#19706)
Thanks for the great tool!

I noticed a small typo [in the
docs](https://docs.astral.sh/ruff/rules/none-not-at-end-of-union/): it's
[commutativity](Commutative_property) that makes the order not matter in
type unions, not
[associativity](https://en.wikipedia.org/wiki/Associative_property)
which is something different.

I make the change in this PR.
2025-08-03 16:30:56 +00:00
cristian64 bc6e105c18
Include column numbers in GitLab output format. (#19708) 2025-08-03 12:37:01 +00:00
Micha Reiser 6bd413df6c
[ty] Update salsa (#19710) 2025-08-03 09:18:10 +00:00
Nathaniel Roman 85bd961fd3
[ty] resolve file symlinks in src walk (#19674)
Co-authored-by: Nathaniel Roman <nroman@openai.com>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-08-01 22:52:04 +02:00
Douglas Creager d37911685f
[ty] Correctly instantiate generic class that inherits `__init__` from generic base class (#19693)
This is subtle, and the root cause became more apparent with #19604,
since we now have many more cases of superclasses and subclasses using
different typevars. The issue is easiest to see in the following:

```py
class C[T]:
    def __init__(self, t: T) -> None: ...

class D[U](C[T]):
    pass

reveal_type(C(1))  # revealed: C[int]
reveal_type(D(1))  # should be: D[int]
```

When instantiating a generic class, the `__init__` method inherits the
generic context of that class. This lets our call binding machinery
infer a specialization for that context.

Prior to this PR, the instantiation of `C` worked just fine. Its
`__init__` method would inherit the `[T]` generic context, and we would
infer `{T = int}` as the specialization based on the argument
parameters.

It didn't work for `D`. The issue is that the `__init__` method was
inheriting the generic context of the class where `__init__` was defined
(here, `C` and `[T]`). At the call site, we would then infer `{T = int}`
as the specialization — but that wouldn't help us specialize `D[U]`,
since `D` does not have `T` in its generic context!

Instead, the `__init__` method should inherit the generic context of the
class that we are performing the lookup on (here, `D` and `[U]`). That
lets us correctly infer `{U = int}` as the specialization, which we can
successfully apply to `D[U]`.

(Note that `__init__` refers to `C`'s typevars in its signature, but
that's okay; our member lookup logic already applies the `T = U`
specialization when returning a member of `C` while performing a lookup
on `D`, transforming its signature from `(Self, T) -> None` to `(Self,
U) -> None`.)

Closes https://github.com/astral-sh/ty/issues/588
2025-08-01 15:29:18 -04:00
GiGaGon 580577e667
[`refurb`] Make example error out-of-the-box (`FURB157`) (#19695)
## Summary

Part of #18972

This PR makes [verbose-decimal-constructor
(FURB157)](https://docs.astral.sh/ruff/rules/verbose-decimal-constructor/#verbose-decimal-constructor-furb157)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/0930015c-ad45-4490-800e-66ed057bfe34)
```py
Decimal("0")
Decimal(float("Infinity"))
```

[New example](https://play.ruff.rs/516e5992-322f-4203-afe7-46d8cad53368)
```py
from decimal import Decimal

Decimal("0")
Decimal(float("Infinity"))
```

Imports were also added to the "Use Instead" section.
2025-08-01 12:55:48 -05:00
Dan Parizher dce25da19a
[`flake8-errmsg`] Exclude `typing.cast` from `EM101` (#19656)
## Summary

Fixes #19596
2025-08-01 13:37:44 -04:00
Douglas Creager 06cd249a9b
[ty] Track different uses of legacy typevars, including context when rendering typevars (#19604)
This PR introduces a few related changes:

- We now keep track of each time a legacy typevar is bound in a
different generic context (e.g. class, function), and internally create
a new `TypeVarInstance` for each usage. This means the rest of the code
can now assume that salsa-equivalent `TypeVarInstance`s refer to the
same typevar, even taking into account that legacy typevars can be used
more than once.

- We also go ahead and track the binding context of PEP 695 typevars.
That's _much_ easier to track since we have the binding context right
there during type inference.

- With that in place, we can now include the name of the binding context
when rendering typevars (e.g. `T@f` instead of `T`)
2025-08-01 12:20:32 -04:00
David Peter 48d5bd13fa
[ty] Initial test suite for `TypedDict` (#19686)
## Summary

Adds an initial set of tests based on the highest-priority items in
https://github.com/astral-sh/ty/issues/154. This is certainly not yet
exhaustive (required/non-required, `total`, and other things are
missing), but will be useful to measure progress on this feature.

## Test Plan

Checked intended behavior against runtime and other type checkers.
2025-08-01 16:56:02 +02:00
Alex Waygood e7e7b7bf21
[ty] Improve debuggability of protocol types (#19662) 2025-08-01 15:16:13 +01:00
Alex Waygood 57e2e8664f
[ty] Simplify lifetime requirements for `PySlice` trait (#19687) 2025-08-01 15:13:47 +01:00
Alex Waygood 18aae21b9a
[ty] Improve `isinstance()` truthiness analysis for generic types (#19668) 2025-08-01 14:44:22 +01:00
GiGaGon d8151f0239
[`refurb`] Make example error out-of-the-box (`FURB164`) (#19673)
<!--
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 [unnecessary-from-float
(FURB164)](https://docs.astral.sh/ruff/rules/unnecessary-from-float/#unnecessary-from-float-furb164)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/807ef72f-9671-408d-87ab-8b8bad65b33f)
```py
Decimal.from_float(4.2)
Decimal.from_float(float("inf"))
Fraction.from_float(4.2)
Fraction.from_decimal(Decimal("4.2"))
```

[New example](https://play.ruff.rs/303680d1-8a68-4b6c-a5fd-d79c56eb0f88)
```py
from decimal import Decimal
from fractions import Fraction

Decimal.from_float(4.2)
Decimal.from_float(float("inf"))
Fraction.from_float(4.2)
Fraction.from_decimal(Decimal("4.2"))
```

The "Use instead" section also had imports added, and one of the fixed
examples was slightly wrong and needed modification.

## Test Plan

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

N/A, no functionality/tests affected
2025-08-01 07:49:58 -05:00
Hunter Hogan 2ee56735e2
Fix link: unused_import.rs (#19648) 2025-08-01 08:47:52 +00:00
David Peter ade6a4262a
[ty] Remove `Specialization::display` (full) (#19682)
## Summary

This seems to be unused
2025-08-01 10:44:11 +02:00
David Peter d43e6fb9c6
[ty] Remove `KnownModule::is_enum` (#19681)
## Summary

Changes the visibility of `KnownModule` and removes an unneeded
function.
2025-08-01 10:31:12 +02:00
Matthew Mckee b30d97e5e0
[ty] Support `__setitem__` and improve `__getitem__` related diagnostics (#19578)
## Summary

Adds validation to subscript assignment expressions.

```py
class Foo: ...

class Bar:
    __setattr__ = None

class Baz:
    def __setitem__(self, index: str, value: int) -> None:
        pass

# We now emit a diagnostic on these statements
Foo()[1] = 2
Bar()[1] = 2
Baz()[1] = 2

```

Also improves error messages on invalid `__getitem__` expressions

## Test Plan

Update mdtests and add more to `subscript/instance.md`

---------

Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
Co-authored-by: David Peter <mail@david-peter.de>
2025-08-01 09:23:27 +02:00
github-actions[bot] 5c5d50d57a
[ty] Sync vendored typeshed stubs (#19676)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2025-08-01 08:43:42 +02:00
Dan Parizher b3a26a50ad
[`flake8-use-pathlib`] Expand `PTH201` to check all `PurePath` subclasses (#19440)
## Summary

Fixes #19437
2025-07-31 22:18:07 -04:00
GiGaGon 6a2d358d7a
[`refurb`] Make example error out-of-the-box (`FURB180`) (#19672)
<!--
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 [meta-class-abc-meta
(FURB180)](https://docs.astral.sh/ruff/rules/meta-class-abc-meta/#meta-class-abc-meta-furb180)'s
example error out-of-the-box.

[Old example](https://play.ruff.rs/6beca1be-45cd-4e5a-aafa-6a0584c10d64)
```py
class C(metaclass=ABCMeta):
    pass
```

[New example](https://play.ruff.rs/bbad34da-bf07-44e6-9f34-53337e8f57d4)
```py
import abc


class C(metaclass=abc.ABCMeta):
    pass
```

The "Use instead" section as also modified similarly.

## Test Plan

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

N/A, no functionality/tests affected
2025-07-31 17:14:15 -04:00
Dan Parizher b07def07c9
[`pyupgrade`] Prevent infinite loop with `I002` (`UP010`, `UP035`) (#19413)
## Summary

Fixes #18729 and fixes #16802

## Test Plan

Manually verified via CLI that Ruff no longer enters an infinite loop by
running:
```sh
echo 1 | ruff --isolated check - --select I002,UP010 --fix
```
with `required-imports = ["from __future__ import generator_stop"]` set
in the config, confirming “All checks passed!” and no snapshots were
generated.

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-31 15:17:27 -04:00
Alex Waygood 2ab1502e51
[ty] Improve the `Display` for generic `type[]` types (#19667) 2025-07-31 19:45:01 +01:00
Alex Waygood a3f28baab4
[ty] Refactor `TypeInferenceBuilder::infer_subscript_expression_types` (#19658) 2025-07-31 13:38:43 +00:00
Brent Westbrook a71513bae1
Fix tests on 32-bit architectures (#19652)
Summary
--

Fixes #19640. I'm not sure these are the exact fixes we really want, but
I
reproduced the issue in a 32-bit Docker container and tracked down the
causes,
so I figured I'd open a PR.

As I commented on the issue, the `goto_references` test depends on the
iteration
order of the files in an `FxHashSet` in `Indexed`. In this case, we can
just
sort the output in test code.

Similarly, the tuple case depended on the order of overloads inserted in
an
`FxHashMap`. `FxIndexMap` seemed like a convenient drop-in replacement,
but I
don't know if that will have other detrimental effects. I did have to
change the
assertion for the tuple test, but I think it should now be stable across
architectures.

Test Plan
--

Running the tests in the aforementioned Docker container
2025-07-31 08:52:19 -04:00
Alex Waygood d2d4b115e3
[ty] Move `pandas-stubs` to bad.txt (#19659) 2025-07-31 12:33:24 +01:00
Alex Waygood 27b03a9d7b
[ty] Remove special casing for string-literal-in-tuple `__contains__` (#19642) 2025-07-31 11:28:03 +01:00
Ibraheem Ahmed 8f8c39c435
Simplify `get_size2` usage (#19643)
## Summary

These were added in the 0.5.0 release.
2025-07-30 15:31:37 -04:00
Matthew Mckee 4739bc8d14
[ty] Fix incorrect diagnostic when calling `__setitem__` (#19645)
## Summary

Resolves https://github.com/astral-sh/ty/issues/862 by not emitting a
diagnostic.

## Test Plan

Add test to show we don't emit the diagnostic
2025-07-30 20:34:52 +02:00
Alex Waygood 7b4103bcb6
[ty] Remove special casing for tuple addition (#19636) 2025-07-30 16:25:42 +00:00
Jim Hoekstra 38049aae12
fix missing-required-imports introducing syntax error after dosctring ending with backslash (#19505)
Issue: https://github.com/astral-sh/ruff/issues/19498

## Summary


[missing-required-import](https://docs.astral.sh/ruff/rules/missing-required-import/)
inserts the missing import on the line immediately following the last
line of the docstring. However, if the dosctring is immediately followed
by a continuation token (i.e. backslash) then this leads to a syntax
error because Python interprets the docstring and the inserted import to
be on the same line.

The proposed solution in this PR is to check if the first token after a
file docstring is a continuation character, and if so, to advance an
additional line before inserting the missing import.

## Test Plan

Added a unit test, and the following example was verified manually:

Given this simple test Python file:

```python
"Hello, World!"\

print(__doc__)
```

and this ruff linting configuration in the `pyproject.toml` file:

```toml
[tool.ruff.lint]
select = ["I"]

[tool.ruff.lint.isort]
required-imports = ["import sys"]
```

Without the changes in this PR, the ruff linter would try to insert the
missing import in line 2, resulting in a syntax error, and report the
following:

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

With the changes in this PR, ruff correctly advances one more line
before adding the missing import, resulting in the following output:

```python
"Hello, World!"\

import sys

print(__doc__)
```

---------

Co-authored-by: Jim Hoekstra <jim.hoekstra@pacmed.nl>
2025-07-30 12:12:46 -04:00
Alex Waygood ec3d5ebda2
[ty] Upcast heterogeneous and mixed tuples to homogeneous tuples where it's necessary to solve a `TypeVar` (#19635)
## Summary

This PR improves our generics solver such that we are able to solve the
`TypeVar` in this snippet to `int | str` (the union of the elements in
the heterogeneous tuple) by upcasting the heterogeneous tuple to its
pure-homogeneous-tuple supertype:

```py
def f[T](x: tuple[T, ...]) -> T:
    return x[0]

def g(x: tuple[int, str]):
    reveal_type(f(x))
```

## Test Plan

Mdtests. Some TODOs remain in the mdtest regarding solving `TypeVar`s
for mixed tuples, but I think this PR on its own is a significant step
forward for our generics solver when it comes to tuple types.

---------

Co-authored-by: Douglas Creager <dcreager@dcreager.net>
2025-07-30 17:12:21 +01:00
Micha Reiser d797592f70
[ty] Fix server panic in workspace diagnostics request handler when typing (#19631) 2025-07-30 16:40:42 +01:00
David Peter eb02aa5676
[ty] Async for loops and async iterables (#19634)
## Summary

Add support for `async for` loops and async iterables.

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

## Ecosystem impact

```diff
- boostedblob/listing.py:445:54: warning[unused-ignore-comment] Unused blanket `type: ignore` directive
```

This is correct. We now find a true positive in the `# type: ignore`'d
code.

All of the other ecosystem hits are of the type

```diff
trio (https://github.com/python-trio/trio)
+ src/trio/_core/_tests/test_guest_mode.py:532:24: error[not-iterable] Object of type `MemorySendChannel[int] | MemoryReceiveChannel[int]` may not be iterable
```

The message is correct, because only `MemoryReceiveChannel` has an
`__aiter__` method, but `MemorySendChannel` does not. What's not correct
is our inferred type here. It should be `MemoryReceiveChannel[int]`, not
the union of the two. This is due to missing unpacking support for tuple
subclasses, which @AlexWaygood is working on. I don't think this should
block merging this PR, because those wrong types are already there,
without this PR.

## Test Plan

New Markdown tests and snapshot tests for diagnostics.
2025-07-30 17:40:24 +02:00
Dan Parizher e593761232
[`ruff`] Parenthesize generator expressions in f-strings (`RUF010`) (#19434)
## Summary

Fixes #19433

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-30 15:02:31 +00:00
Brent Westbrook 8979271ea8
Always expand tabs to four spaces in diagnostics (#19618)
## Summary

I was a bit stuck on some snapshot differences I was seeing in #19415,
but @BurntSushi pointed out that `annotate-snippets` already normalizes
tabs on its own, which was very helpful! Instead of applying this change
directly to the other branch, I wanted to try applying it in
`ruff_linter` first. This should very slightly reduce the number of
changes in #19415 proper.

It looks like `annotate-snippets` always expands a tab to four spaces,
whereas I think we were aligning to tab stops:

```diff
  6 | spam(ham[1], { eggs: 2})
  7 | #: E201:1:6
- 8 | spam(   ham[1], {eggs: 2})
-   |      ^^^ E201
+ 8 | spam(    ham[1], {eggs: 2})
+   |      ^^^^ E201
```

```diff
61 | #: E203:2:15 E702:2:16
 62 | if x == 4:
-63 |     print(x, y) ; x, y = y, x
-   |                ^ E203
+63 |     print(x, y)    ; x, y = y, x
+   |                ^^^^ E203
```

```diff
 E27.py:15:6: E271 [*] Multiple spaces after keyword
    |
-13 | True        and False
+13 | True        and    False
 14 | #: E271
 15 | a and  b
    |      ^^ E271
```

I don't think this is too bad and has the major benefit of allowing us
to pass the non-tab-expanded range to `annotate-snippets` in #19415,
where it's also displayed in the header. Ruff doesn't have this problem
currently because it uses its own concise diagnostic output as the
header for full diagnostics, where the pre-expansion range is used
directly.

## Test Plan

Existing tests with a few snapshot updates
2025-07-30 11:00:36 -04:00
Andrew Gallant d1a286226c [ty] Update module resolution diagram to account for typeshed `VERSIONS` file
This does unfortunately add a fair bit of complexity to the flow
diagram.

Ref https://github.com/astral-sh/ruff/pull/19620#issuecomment-3133684294
2025-07-30 10:34:58 -04:00
Micha Reiser 70d4b271da
[ty] Remove `AssertUnwindSafe` requirement from `ProgressReporter` (#19637) 2025-07-30 12:46:44 +00:00
Alex Waygood feaedb1812
[ty] Synthesize precise `__getitem__` overloads for tuple subclasses (#19493) 2025-07-30 11:25:44 +00:00
Micha Reiser 6237ecb4db
[ty] Add progress reporting to workspace diagnostics (#19616) 2025-07-30 10:27:34 +00:00
Micha Reiser 2a5ace6e55
[ty] Implement diagnostic caching (#19605) 2025-07-30 11:04:34 +01:00
David Peter 4ecf1d205a
[ty] Support `async`/`await`, `async with` and `yield from` (#19595)
## Summary

- Add support for the return types of `async` functions
- Add type inference for `await` expressions
- Add support for `async with` / async context managers
- Add support for `yield from` expressions

This PR is generally lacking proper error handling in some cases (e.g.
illegal `__await__` attributes). I'm planning to work on this in a
follow-up.

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

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

## Ecosystem

There are a lot of true positives on `prefect` which look similar to:
```diff
prefect (https://github.com/PrefectHQ/prefect)
+ src/integrations/prefect-aws/tests/workers/test_ecs_worker.py:406:12: error[unresolved-attribute] Type `str` has no attribute `status_code`
```

This is due to a wrong return type annotation
[here](e926b8c4c1/src/integrations/prefect-aws/tests/workers/test_ecs_worker.py (L355-L391)).

```diff
mitmproxy (https://github.com/mitmproxy/mitmproxy)
+ test/mitmproxy/addons/test_clientplayback.py:18:1: error[invalid-argument-type] Argument to function `asynccontextmanager` is incorrect: Expected `(...) -> AsyncIterator[Unknown]`, found `def tcp_server(handle_conn, **server_args) -> Unknown | tuple[str, int]`
```


[This](a4d794c59a/test/mitmproxy/addons/test_clientplayback.py (L18-L19))
is a true positive. That function should return
`AsyncIterator[Address]`, not `Address`.

I looked through almost all of the other new diagnostics and they all
look like known problems or true positives.

## Typing conformance

The typing conformance diff looks good.

## Test Plan

New Markdown tests
2025-07-30 11:51:21 +02:00
Brent Westbrook c5ac998892
Bump 0.12.7 (#19627)
## Test Plan

- [x] Download the [sdist
artifact](https://github.com/astral-sh/ruff/actions/runs/16608501774/artifacts/3643617012)
and check that the LICENSE is present
2025-07-29 18:18:42 -04:00
Brent Westbrook 6e00adf308
Bump 0.12.6 (#19622) 2025-07-29 16:31:01 -04:00
Brent Westbrook 864196b988
Add `Checker::context` method, deduplicate Unicode checks (#19609)
Summary
--

This PR adds a `Checker::context` method that returns the underlying
`LintContext` to unify `Candidate::into_diagnostic` and
`Candidate::report_diagnostic` in our ambiguous Unicode character
checks. This avoids some duplication and also avoids collecting a `Vec`
of `Candidate`s only to iterate over it later.

Test Plan
--

Existing tests
2025-07-29 16:07:55 -04:00
Thomas Mattone ae26fa020c
[`flake8-pyi`] Preserve inline comment in ellipsis removal (`PYI013`) (#19399)
## Summary

Fixes #19385.

Based on [unnecessary-placeholder
(PIE790)](https://docs.astral.sh/ruff/rules/unnecessary-placeholder/)
behavior, [ellipsis-in-non-empty-class-body
(PYI013)](https://docs.astral.sh/ruff/rules/ellipsis-in-non-empty-class-body/)
now safely preserve inline comment on ellipsis removal.

## Test Plan

A new test class was added:

```python
class NonEmptyChildWithInlineComment:
    value: int
    ... # preserve me
```

with the following expected fix:

```python
class NonEmptyChildWithInlineComment:
    value: int
    # preserve me
```
2025-07-29 15:06:04 -04:00
Andrew Gallant 88a679945c [ty] Add flow diagram for import resolution
The diagram is written in the Dot language, which can
be converted to SVG (or any other image) by GraphViz.

I thought it was a good idea to write this down in
preparation for adding routines that list modules.
Code reuse is likely to be difficult and I wanted to
be sure I understood how it worked.
2025-07-29 14:49:20 -04:00
Andrew Gallant 941be52358 [ty] Add comments to some core resolver functions
Some of the contracts were a little tricky to discover from just the
parameter types, so I added some docs (and fixed what I believe was one
typo).
2025-07-29 14:49:20 -04:00
Andrew Gallant 13624ce17f [ty] Add missing ticks and use consistent quoting
This irked me while I was reading the code, so I just tried to fix what
I could see.
2025-07-29 14:49:20 -04:00
Andrew Gallant edb2f8e997 [ty] Reflow some long lines
I mostly just did this because the long string literals were annoying
me. And these can make rustfmt give up on formatting.

I also re-flowed some long comment lines while I was here.
2025-07-29 14:49:20 -04:00
Andrew Gallant 5e6ad849ff [ty] Unexport helper function
I'm not sure if this used to be used elsewhere, but it no longer is.
And it looks like an internal-only helper function, so just un-export
it.

And note that `ModuleNameIngredient` is also un-exported, so this
function isn't really usable outside of its defining module anyway.
2025-07-29 14:49:20 -04:00
Andrew Gallant 865a9b3424 [ty] Remove offset from `CompletionTargetTokens::Unknown`
At some point, the surrounding code was refactored so that the
cursor offset was always passed around, so storing it here is
no longer necessary.
2025-07-29 14:49:20 -04:00
Dan Parizher d449c541cb
[`pyupgrade`] Fix `UP030` to avoid modifying double curly braces in format strings (#19378)
## Summary

Fixes #19348

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-29 18:35:54 +00:00
हिमांशु f7c6a6b2d0
[ty] fix a typo (#19621)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-29 17:53:44 +00:00
justin 656273bf3d
[ty] synthesize `__replace__` for dataclasses (>=3.13) (#19545)
## Summary
https://github.com/astral-sh/ty/issues/111

adds support for the new `copy.replace` and `__replace__` protocol
[added in 3.13](https://docs.python.org/3/whatsnew/3.13.html#copy)

- docs: https://docs.python.org/3/library/copy.html#object.__replace__
- some discussion on pyright/mypy implementations:
https://discuss.python.org/t/dataclass-transform-and-replace/69067



### Burndown
- [x] add tests
- [x] implement `__replace__`
- [ ]
[collections.namedtuple()](https://docs.python.org/3/library/collections.html#collections.namedtuple)
- [x]
[dataclasses.dataclass](https://docs.python.org/3/library/dataclasses.html#dataclasses.dataclass)

## Test Plan
new mdtests

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-29 17:32:01 +02:00
Alex Waygood 81867ea7ce
[ty] Discard `Definition`s when normalizing `Signature`s (#19615) 2025-07-29 14:37:47 +01:00
Brent Westbrook a54061e757
[ty] Fix empty spans following a line terminator and unprintable character spans in diagnostics (#19535)
## Summary

This was previously the last commit in #19415, split out to make it
easier to review. This applies the fixes from c9b99e4, 5021f32, and
2922490cb8 to the new rendering code in `ruff_db`. I initially intended
only to fix the empty span after a line terminator (as you can see in
the branch name), but the two fixes were tied pretty closely together,
and my initial fix for the empty spans needed a big change after trying
to handle unprintable characters too. I can still split this up if it
would help with review. I would just start with the unprintable
characters first.

The implementation here is essentially copy-pasted from
`ruff_linter::message::text.rs`, with the `SourceCode` struct renamed to
`EscapedSourceCode` since there's already a `SourceCode` in scope in
`render.rs`. It's also updated slightly to account for the multiple
annotations for a single snippet. The original implementation used some
types from the `line_width` module from `ruff_linter`. I copied over
heavily stripped-down versions of these instead of trying to import
them. We could inline the remaining code entirely, if we want, but I
thought it was nice enough to keep.

I also moved over `ceil_char_boundary`, which is unchanged except to
make it a free function taking a `&str` instead of a `Locator` method.
All of this code could be deleted from `ruff_linter` if we also move
over the `grouped` output format, which will be the last user after
#19415.

## Test Plan

I added new tests in `ruff_linter` that call into the new rendering code
to snapshot the diagnostics for the affected cases. These are copies of
existing snapshots in Ruff, so it's helpful to compare them. These are a
bit noisy because of the other rendering differences in the header, but
all of the `^^^` indicators should be the same.

<details><summary>`empty_span_after_line_terminator` diff</summary>

```diff
diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__empty_span_after_line_terminator.snap
index 5ade4346e0..6df75c16f0 100644
--- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E112_E11.py.snap
+++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__empty_span_after_line_terminator.snap
@@ -1,17 +1,20 @@
 ---
-source: crates/ruff_linter/src/rules/pycodestyle/mod.rs
+source: crates/ruff_linter/src/message/text.rs
+expression: value.to_string()
 ---
-E11.py:9:1: E112 Expected an indented block
+error[no-indented-block]: Expected an indented block
+  --> E11.py:9:1
    |
  7 | #: E112
  8 | if False:
  9 | print()
-   | ^ E112
+   | ^
 10 | #: E113
 11 | print()
    |
 
-E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
+error[invalid-syntax]: SyntaxError: Expected an indented block after `if` statement
+  --> E11.py:9:1
    |
  7 | #: E112
  8 | if False:
@@ -21,7 +24,8 @@ E11.py:9:1: SyntaxError: Expected an indented block after `if` statement
 11 | print()
    |
 
-E11.py:12:1: SyntaxError: Unexpected indentation
+error[invalid-syntax]: SyntaxError: Unexpected indentation
+  --> E11.py:12:1
    |
 10 | #: E113
 11 | print()
@@ -31,7 +35,8 @@ E11.py:12:1: SyntaxError: Unexpected indentation
 14 | mimetype = 'application/x-directory'
    |
 
-E11.py:14:1: SyntaxError: Expected a statement
+error[invalid-syntax]: SyntaxError: Expected a statement
+  --> E11.py:14:1
    |
 12 |     print()
 13 | #: E114 E116
@@ -41,17 +46,19 @@ E11.py:14:1: SyntaxError: Expected a statement
 16 | create_date = False
    |
 
-E11.py:45:1: E112 Expected an indented block
+error[no-indented-block]: Expected an indented block
+  --> E11.py:45:1
    |
 43 | #: E112
 44 | if False:  #
 45 | print()
-   | ^ E112
+   | ^
 46 | #:
 47 | if False:
    |
 
-E11.py:45:1: SyntaxError: Expected an indented block after `if` statement
+error[invalid-syntax]: SyntaxError: Expected an indented block after `if` statement
+  --> E11.py:45:1
    |
 43 | #: E112
 44 | if False:  #
```

</details>

<details><summary>`unprintable_characters` diff</summary>

```diff
diff --git a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__unprintable_characters.snap
index 52cfdf9cce..fcfa1ac9f1 100644
--- a/crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLE2512_invalid_characters.py.snap
+++ b/crates/ruff_linter/src/message/snapshots/ruff_linter__message__text__tests__unprintable_characters.snap
@@ -1,161 +1,115 @@
 ---
-source: crates/ruff_linter/src/rules/pylint/mod.rs
+source: crates/ruff_linter/src/message/text.rs
+expression: value.to_string()
 ---
-invalid_characters.py:24:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:24:12
    |
 22 | cr_ok = f'\\r'
 23 |
 24 | sub = 'sub '
-   |            ^ PLE2512
+   |            ^
 25 | sub = f'sub '
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-ℹ Safe fix
-21 21 | cr_ok = '\\r'
-22 22 | cr_ok = f'\\r'
-23 23 | 
-24    |-sub = 'sub '
-   24 |+sub = 'sub \x1A'
-25 25 | sub = f'sub '
-26 26 | 
-27 27 | sub_ok = '\x1a'
-
-invalid_characters.py:25:13: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:25:13
    |
 24 | sub = 'sub '
 25 | sub = f'sub '
-   |             ^ PLE2512
+   |             ^
 26 |
 27 | sub_ok = '\x1a'
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-22 22 | cr_ok = f'\\r'
-23 23 | 
-24 24 | sub = 'sub '
-25    |-sub = f'sub '
-   25 |+sub = f'sub \x1A'
-26 26 | 
-27 27 | sub_ok = '\x1a'
-28 28 | sub_ok = f'\x1a'
+help: Replace with escape sequence
 
-invalid_characters.py:55:25: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:55:25
    |
 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
 54 |
 55 | nested_fstrings = f'␈{f'{f'␛'}'}'
-   |                         ^ PLE2512
+   |                         ^
 56 |
 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-52 52 | zwsp_after_multicharacter_grapheme_cluster = "ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
-53 53 | zwsp_after_multicharacter_grapheme_cluster = f"ಫ್ರಾನ್ಸಿಸ್ಕೊ ​​"
-54 54 | 
-55    |-nested_fstrings = f'␈{f'{f'␛'}'}'
-   55 |+nested_fstrings = f'␈{f'\x1A{f'␛'}'}'
-56 56 | 
-57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
-58 58 | x = f"""}}ab"""
+help: Replace with escape sequence
 
-invalid_characters.py:58:12: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:58:12
    |
 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
 58 | x = f"""}}ab"""
-   |            ^ PLE2512
+   |            ^
 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256
 60 | x = f"""}}a␛b"""
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-ℹ Safe fix
-55 55 | nested_fstrings = f'␈{f'{f'␛'}'}'
-56 56 | 
-57 57 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998106
-58    |-x = f"""}}ab"""
-   58 |+x = f"""}}a\x1Ab"""
-59 59 | # https://github.com/astral-sh/ruff/issues/7455#issuecomment-1741998256
-60 60 | x = f"""}}a␛b"""
-61 61 | 
-
-invalid_characters.py:64:12: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:64:12
    |
 63 | # https://github.com/astral-sh/ruff/issues/13294
 64 | print(r"""␈␛�​
-   |            ^ PLE2512
+   |            ^
 65 | """)
 66 | print(fr"""␈␛�​
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:66:13: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:66:13
    |
 64 | print(r"""␈␛�​
 65 | """)
 66 | print(fr"""␈␛�​
-   |             ^ PLE2512
+   |             ^
 67 | """)
 68 | print(Rf"""␈␛�​
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:68:13: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:68:13
    |
 66 | print(fr"""␈␛�​
 67 | """)
 68 | print(Rf"""␈␛�​
-   |             ^ PLE2512
+   |             ^
 69 | """)
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:73:9: PLE2512 Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:73:9
    |
 71 | # https://github.com/astral-sh/ruff/issues/18815
 72 | b = "\␈"
 73 | sub = "\"
-   |         ^ PLE2512
+   |         ^
 74 | esc = "\␛"
 75 | zwsp = "\​"
    |
-   = help: Replace with escape sequence
+help: Replace with escape sequence
 
-invalid_characters.py:80:25: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:80:25
    |
 78 | # tstrings
 79 | esc = t'esc esc ␛'
 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
-   |                         ^ PLE2512
+   |                         ^
 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-77 77 | 
-78 78 | # tstrings
-79 79 | esc = t'esc esc ␛'
-80    |-nested_tstrings = t'␈{t'{t'␛'}'}'
-   80 |+nested_tstrings = t'␈{t'\x1A{t'␛'}'}'
-81 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
-82 82 | 
+help: Replace with escape sequence
 
-invalid_characters.py:81:26: PLE2512 [*] Invalid unescaped character SUB, use "\x1A" instead
+error[invalid-character-sub]: Invalid unescaped character SUB, use "\x1A" instead
+  --> invalid_characters.py:81:26
    |
 79 | esc = t'esc esc ␛'
 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
 81 | nested_ftstrings = t'␈{f'{t'␛'}'}'
-   |                          ^ PLE2512
+   |                          ^
    |
-   = help: Replace with escape sequence
-
-ℹ Safe fix
-78 78 | # tstrings
-79 79 | esc = t'esc esc ␛'
-80 80 | nested_tstrings = t'␈{t'{t'␛'}'}'
-81    |-nested_ftstrings = t'␈{f'{t'␛'}'}'
-   81 |+nested_ftstrings = t'␈{f'\x1A{t'␛'}'}'
-82 82 |
+help: Replace with escape sequence
```

</details>
2025-07-29 08:25:58 -04:00
Brent Westbrook 19569bf838
Add `LinterContext::settings` to avoid passing separate settings (#19608)
Summary
--

I noticed while reviewing #19390 that in `check_tokens` we were still
passing
around an extra `LinterSettings`, despite all of the same functions also
receiving a `LintContext` with its own settings.

This PR adds the `LintContext::settings` method and calls that instead
of using
the separate `LinterSettings`.

Test Plan
--

Existing tests
2025-07-29 08:13:22 -04:00
Charlie Marsh e0f4f25d28
Support `.pyi` files in ruff analyze graph (#19611)
## Summary

We now return both the `.pyi` and `.py` files. Previously, we only
returned the `.pyi` file.
2025-07-28 22:00:27 -04:00
github-actions[bot] c6a123290d
[ty] Sync vendored typeshed stubs (#19607)
Co-authored-by: typeshedbot <>
Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
2025-07-28 22:06:33 +00:00
Igor Drokin e4f64480da
[`perflint`] Ignore rule if target is `global` or `nonlocal` (`PERF401`) (#19539)
## Summary

Resolves #19531

I've implemented a check to determine whether the for_stmt target is
declared as global or nonlocal. I believe we should skip the rule in all
such cases, since variables declared this way are intended for use
outside the loop scope, making value changes expected behavior.

## Test Plan

Added two test cases for global and nonlocal variable to snapshot.
2025-07-28 17:03:22 -04:00
UnboundVariable 24134837f3
[ty] Add stub mapping support to signature help (#19570)
This PR improves the "signature help" language server feature in two
ways:
1. It adds support for the recently-introduced "stub mapper" which maps
symbol declarations within stubs to their implementation counterparts.
This allows the signature help to display docstrings from the original
implementation.
2. It incorporates a more robust fix to a bug that was addressed in a
[previous PR](https://github.com/astral-sh/ruff/pull/19542). It also
adds more comprehensive tests to cover this case.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-28 10:57:18 -07:00
Douglas Creager 130d4e1135
[ty] Don't panic with argument that doesn't actually implement Iterable (#19602)
This eliminates the panic reported in
https://github.com/astral-sh/ty/issues/909, though it doesn't address
the underlying cause, which is that we aren't yet checking the types of
the fields of a protocol when checking whether a class implements the
protocol. And in particular, if a class explictly opts out of iteration
via

```py
class NotIterable:
    __iter__ = None
```

we currently treat that as "having an `__iter__`" member, and therefore
implementing `Iterable`.

Note that the assumption that was in the comment before is still
correct: call binding will have already checked that the argument
satisfies `Iterable`, and so it shouldn't be an error to iterate over
said argument. But arguably, the new logic in this PR is a better way to
discharge that assumption — instead of panicking if we happen to be
wrong, fall back on an unknown iteration result.
2025-07-28 12:09:54 -04:00
Dan Parizher e63dfa3d18
[`flake8-commas`] Add support for trailing comma checks in type parameter lists (`COM812`,`COM819`) (#19390)
## Summary

Fixes #18844

I'm not too sure if the solution is as simple as the way I implemented
it, but I'm curious to see if we are covering all cases correctly here.

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2025-07-28 10:53:04 -04:00
Junhson Jean-Baptiste 6d0f3ef3a5
[`pylint`] Implement auto-fix for `missing-maxsplit-arg` (`PLC0207`) (#19387)
<!--
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 a follow-up to #18949 (suggested
[here](https://github.com/astral-sh/ruff/pull/18949#pullrequestreview-2998417889)),
this PR implements auto-fix logic for `PLC0207`.

## Test Plan

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

Existing tests pass, with updates to the snapshot so that it expects the
new output that comes along with the auto-fix.
2025-07-28 10:45:26 -04:00
Dan Parizher 201b079084
[`refurb`] Mark `int` and `bool` cases for `Decimal.from_float` as safe fixes in `FURB164` tests (#19468)
## Summary

Fixes #19460

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-28 14:21:38 +00:00
David Peter 2680f2ed81
[ty] Minor: test isolation (#19597)
## Summary

Split the "Generator functions" tests into two parts. The first part
(synchronous) refers to a function called `i` from a function `i2`. But
`i` is later redeclared in the asynchronous part, which was probably not
intended.
2025-07-28 15:52:59 +02:00
Micha Reiser afdfa042f3
[ty] Remove `AssertUnwindSafe` from `BackgroundRequestHandler` api (#19598) 2025-07-28 13:28:09 +00:00
Dylan 008bbfdf5a
Disallow implicit concatenation of t-strings and other string types (#19485)
As of [this cpython PR](https://github.com/python/cpython/pull/135996),
it is not allowed to concatenate t-strings with non-t-strings,
implicitly or explicitly. Expressions such as `"foo" t"{bar}"` are now
syntax errors.

This PR updates some AST nodes and parsing to reflect this change.

The structural change is that `TStringPart` is no longer needed, since,
as in the case of `BytesStringLiteral`, the only possibilities are that
we have a single `TString` or a vector of such (representing an implicit
concatenation of t-strings). This removes a level of nesting from many
AST expressions (which is what all the snapshot changes reflect), and
simplifies some logic in the implementation of visitors, for example.

The other change of note is in the parser. When we meet an implicit
concatenation of string-like literals, we now count the number of
t-string literals. If these do not exhaust the total number of
implicitly concatenated pieces, then we emit a syntax error. To recover
from this syntax error, we encode any t-string pieces as _invalid_
string literals (which means we flag them as invalid, record their
range, and record the value as `""`). Note that if at least one of the
pieces is an f-string we prefer to parse the entire string as an
f-string; otherwise we parse it as a string.

This logic is exactly the same as how we currently treat
`BytesStringLiteral` parsing and error recovery - and carries with it
the same pros and cons.

Finally, note that I have not implemented any changes in the
implementation of the formatter. As far as I can tell, none are needed.
I did change a few of the fixtures so that we are always concatenating
t-strings with t-strings.
2025-07-27 12:41:03 +00:00
Alex Waygood df5eba7583
[ty] Mark `all_type_assignable_to_iterable_are_iterable` as flaky (#19574) 2025-07-27 11:04:13 +00:00
Micha Reiser 469c50b0b7
[ty] Support stdlib files in playground (#19557) 2025-07-26 19:33:38 +01:00
UnboundVariable 738246627f
[ty] Implemented support for "selection range" language server feature (#19567)
This PR adds support for the "selection range" language server feature.
This feature was recently requested by a ty user in [this feature
request](https://github.com/astral-sh/ty/issues/882).

This feature allows a client to implement "smart selection expansion"
based on the structure of the parse tree. For example, if you type
"shift-ctrl-right-arrow" in VS Code, the current selection will be
expanded to include the parent AST node. Conversely,
"shift-ctrl-left-arrow" shrinks the selection.

We will probably need to tune the granularity of selection expansion
based on user feedback. The initial implementation includes most AST
nodes, but users may find this to be too fine-grained. We have the
option of skipping some AST nodes that are not as meaningful when
editing code.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-26 09:08:36 -07:00
Douglas Creager e867830848
[ty] Don't include already-bound legacy typevars in function generic context (#19558)
We now correctly exclude legacy typevars from enclosing scopes when
constructing the generic context for a generic function.

more detail:

A function is generic if it refers to legacy typevars in its signature:

```py
from typing import TypeVar

T = TypeVar("T")

def f(t: T) -> T:
    return t
```

Generic functions are allowed to appear inside of other generic
contexts. When they do, they can refer to the typevars of those
enclosing generic contexts, and that should not rebind the typevar:

```py
from typing import TypeVar, Generic

T = TypeVar("T")
U = TypeVar("U")

class C(Generic[T]):
    @staticmethod
    def method(t: T, u: U) -> None: ...

# revealed: def method(t: int, u: U) -> None
reveal_type(C[int].method)
```

This substitution was already being performed correctly, but we were
also still including the enclosing legacy typevars in the method's own
generic context, which can be seen via `ty_extensions.generic_context`
(which has been updated to work on generic functions and methods):

```py
from ty_extensions import generic_context

# before: tuple[T, U]
# after: tuple[U]
reveal_type(generic_context(C[int].method))
```

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-25 18:14:19 -04:00
Elliot Simpson 72fdb7d439
[`flake8-blind-except`] Change `BLE001` to permit `logging.critical(..., exc_info=True)`. (#19520)
## Summary

Changing `BLE001` (blind-except) so that it does not flag `except`
clauses which include `logging.critical(..., exc_info=True)`.

## Test Plan

It passes the following (whereas the `main` branch does not):
```sh
$ cargo run -p ruff -- check somefile.py --no-cache --select=BLE001
```
```python
# somefile.py

import logging


try:
    print("Hello world!")
except Exception:
    logging.critical("Did not run.", exc_info=True)
```
Related: https://github.com/astral-sh/ruff/issues/19519
2025-07-25 17:52:58 -04:00
Dylan fbf1dfc782
Reword preview warning for `target-version` Python 3.14 (#19563)
Small rewording to indicate that core development is done but that we
may add breaking changes.

Feel free to bikeshed!

Test:

```console
❯ echo "t''" | cargo run -p ruff -- check --no-cache --isolated --target-version py314 -
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/ruff check --no-cache --isolated --target-version py314 -`
warning: Support for Python 3.14 is in preview and may undergo breaking changes. Enable `preview` to remove this warning.
All checks passed!
```
2025-07-25 16:09:45 -05:00
UnboundVariable a0d8ff51dd
[ty] Added support for "document symbols" and "workspace symbols" (#19521)
This PR adds support for "document symbols" and "workspace symbols"
language server features. Most of the logic to implement these features
is shared.

The "document symbols" feature returns a list of all symbols within a
specified source file. Clients can specify whether they want a flat or
hierarchical list. Document symbols are typically presented by a client
in an "outline" form. Here's what this looks like in VS Code, for
example.

<img width="240" height="249" alt="image"
src="https://github.com/user-attachments/assets/82b11f4f-32ec-4165-ba01-d6496ad13bdf"
/>


The "workspace symbols" feature returns a list of all symbols across the
entire workspace that match some user-supplied query string. This allows
the user to quickly find and navigate to any symbol within their code.

<img width="450" height="134" alt="image"
src="https://github.com/user-attachments/assets/aac131e0-9464-4adf-8a6c-829da028c759"
/>

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-25 13:07:38 -07:00
Brent Westbrook 165091a31c
Add `TextEmitter::with_color` and disable colors in `unreadable_files` test (#19562)
Summary
--

I looked at other uses of `TextEmitter`, and I think this should be the
only one affected by this. The other integration tests must work
properly since they're run with `assert_cmd_snapshot!`, which I assume
triggers the `SHOULD_COLORIZE` case, and the `cfg!(test)` check will
work for uses in `ruff_linter`.


4a4dc38b5b/crates/ruff_linter/src/message/text.rs (L36-L44)

Alternatively, we could probably move this to a CLI test instead.

Test Plan
--

`cargo test -p ruff`, which was failing on `main` with color codes in
the output before this
2025-07-25 15:47:49 -04:00
UnboundVariable 4a4dc38b5b
[ty] Added support for document highlights in playground. (#19540)
This PR adds support for the "document highlights" feature in the ty
playground.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-25 08:55:40 -07:00
Dan Parizher 3e366fdf13
[`refurb`] Ignore decorated functions for `FURB118` (#19339)
## Summary

Fixes #19305
2025-07-25 10:43:17 -05:00
Alex Waygood 859262bd49
[ty] Move `zope.interface` to `good.txt` for primer runs (#19208) 2025-07-25 14:12:17 +01:00
David Peter c0768dfd96
[ty] Attribute access on intersections with negative parts (#19524)
## Summary

We currently infer a `@Todo` type whenever we access an attribute on an
intersection type with negative components. This can happen very
naturally. Consequently, this `@Todo` type is rather pervasive and hides
a lot of true positives that ty could otherwise detect:

```py
class Foo:
    attr: int = 1

def _(f: Foo | None):
    if f:
        reveal_type(f)  # Foo & ~AlwaysFalsy

        reveal_type(f.attr)  # now: int, previously: @Todo
```

The changeset here proposes to handle member access on these
intersection types by simply ignoring all negative contributions. This
is not always ideal: a negative contribution like `~<Protocol with
members 'attr'>` could be a hint that `.attr` should not be accessible
on the full intersection type. The behavior can certainly be improved in
the future, but this seems like a reasonable initial step to get rid of
this unnecessary `@Todo` type.

## Ecosystem analysis

There are quite a few changes here. I spot-checked them and found one
bug where attribute access on pure negation types (`~P == object & ~P`)
would not allow attributes on `object` to be accessed. After that was
fixed, I only see true positives and known problems. The fact that a lot
of `unused-ignore-comment` diagnostics go away are also evidence for the
fact that this touches a sensitive area, where static analysis clashes
with dynamically adding attributes to objects:
```py
… # type: ignore # Runtime attribute access
```

## Test Plan

Updated tests.
2025-07-25 14:56:14 +02:00
David Peter d4eb4277ad
[ty] Add basic support for `dataclasses.field` (#19553)
## Summary

Add basic support for `dataclasses.field`:
* remove fields with `init=False` from the signature of the synthesized
`__init__` method
* infer correct default value types from `default` or `default_factory`
arguments

```py
from dataclasses import dataclass, field

def default_roles() -> list[str]:
    return ["user"]

@dataclass
class Member:
    name: str
    roles: list[str] = field(default_factory=default_roles)
    tag: str | None = field(default=None, init=False)

# revealed: (self: Member, name: str, roles: list[str] = list[str]) -> None
reveal_type(Member.__init__)
```

Support for `kw_only` has **not** been added.

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

## Test Plan

New Markdown tests
2025-07-25 14:56:04 +02:00
Micha Reiser b033fb6bfd
[ty] Split `ScopedPlaceId` into `ScopedSymbolId` and `ScopedMemberId` (#19497) 2025-07-25 13:54:33 +02:00
Alex Waygood f722bfa9e6
[ty] Do not consider a type `T` to satisfy a method member on a protocol unless the method is available on the meta-type of `T` (#19187) 2025-07-25 11:16:04 +01:00
Shunsuke Shibayama b124e182ca
[ty] improve lazy scope place lookup (#19321)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
Co-authored-by: Carl Meyer <carl@oddbird.net>
Co-authored-by: Micha Reiser <micha@reiser.io>
2025-07-25 07:11:11 +00:00
Dhruv Manilawala 57373a7e4d
[ty] Derive `Serialize` unconditionally on client options (#19549) 2025-07-25 04:03:03 +00:00
Carl Meyer ae9d450b5f
[ty] Fallback to Unknown if no type is stored for an expression (#19517)
## Summary

See discussion at
https://github.com/astral-sh/ruff/pull/19478/files#r2223870292

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

## Test Plan

Added one mdtest for invalid Callable annotation; removed `pull-types:
skip` from that test file.

Co-authored-by: lipefree <willy.ngo.2000@gmail.com>
2025-07-25 02:05:32 +00:00
UnboundVariable c8c80e054e
[ty] Fix bug #879 in signature help (#19542)
This PR fixes bug [#879](https://github.com/astral-sh/ty/issues/879)
where the signature help popup remains visible after typing the closing
paren in a call expression.

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-24 16:26:14 -07:00
UnboundVariable 4bc34b82ef
[ty] Added support for "document highlights" language server feature. (#19515)
This PR adds support for the "document highlights" language server
feature.

This feature allows a client to highlight all instances of a selected
name within a document. Without this feature, editors perform
highlighting based on a simple text match. This adds semantic knowledge.

The implementation of this feature largely overlaps that of the
recently-added "references" feature. This PR refactors the existing
"references.rs" module, separating out the functionality and tests that
are specific to the other language feature into a "goto_references.rs"
module. The "references.rs" module now contains the functionality that
is common to "goto references", "document highlights" and "rename"
(which is not yet implemented).

As part of this PR, I also created a new `ReferenceTarget` type which is
similar to the existing `NavigationTarget` type but better suited for
references. This idea was suggested by @MichaReiser in [this code review
feedback](https://github.com/astral-sh/ruff/pull/19475#discussion_r2224061006)
from a previous PR. Notably, this new type contains a field that
specifies the "kind" of the reference (read, write or other). This
"kind" is needed for the document highlights feature.

Before: all textual instances of `foo` are highlighted
<img width="156" height="126" alt="Screenshot 2025-07-23 at 12 51 09 PM"
src="https://github.com/user-attachments/assets/37ccdb2f-d48a-473d-89d5-8e89cb6c394e"
/>

After: only semantic matches are highlighted
<img width="164" height="157" alt="Screenshot 2025-07-23 at 12 52 05 PM"
src="https://github.com/user-attachments/assets/2efadadd-4691-4815-af04-b031e74c81b7"
/>

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-24 13:06:25 -07:00
Charlie Marsh d9cab4d242
Add support for specifying minimum dots in detected string imports (#19538)
## Summary

Defaults to requiring two dots, which matches the Pants default.
2025-07-24 15:48:23 -04:00
David Peter d77b7312b0
[ty] Minor: fix incomplete docstring (#19534) 2025-07-24 21:01:15 +02:00
Dhruv Manilawala f9091ea8bb
[ty] Move server tests as integration tests (#19522)
## Summary

Reference:
https://github.com/astral-sh/ruff/pull/19391#discussion_r2222780892
2025-07-24 16:10:17 +00:00
Robsdedude 1d2181623c
[`ruff`] Offer fixes for `RUF039` in more cases (#19065)
## Summary
Expand cases in which ruff can offer a fix for `RUF039` (some of which
are unsafe).

While turning `"\n"` (== `\n`) into `r"\n"` (== `\\n`) is not equivalent
at run-time, it's still functionally equivalent to do so in the context
of [regex
patterns](https://docs.python.org/3/library/re.html#regular-expression-syntax)
as they themselves interpret the escape sequence. Therefore, an unsafe
fix can be offered.

Further, this PR also makes ruff offer fixes for byte string literals,
not only strings literals as before.

## Test Plan
Tests for all escape sequences have been added.

## Related
Closes: https://github.com/astral-sh/ruff/issues/16713

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-24 11:45:45 -04:00
David Peter dc6be457b5
[ty] Support `dataclasses.InitVar` (#19527)
## Summary

I saw that this creates a lot of false positives in the ecosystem, and
it seemed to be relatively easy to add basic support for this.

Some preliminary work on this was done by @InSyncWithFoo — thank you.

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

## Ecosystem analysis

The results look good.

## Test Plan

New Markdown tests

---------

Co-authored-by: InSync <insyncwithfoo@gmail.com>
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2025-07-24 16:33:33 +02:00
Robsdedude 1079975b35
[`ruff`] Fix `RUF033` breaking with named default expressions (#19115)
## Summary
The generated fix for `RUF033` would cause a syntax error for named
expressions as parameter defaults.
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
    def __post_init__(self, bar: int = (x := 1)) -> None:
        pass
```
would be turned into
```python
from dataclasses import InitVar, dataclass
@dataclass
class Foo:
    x: InitVar[int] = x := 1
    def __post_init__(self, bar: int = (x := 1)) -> None:
        pass
```
instead of the syntactically correct
```python
# ...
x: InitVar[int] = (x := 1)
# ...
```

## Test Plan
Test reproducer (plus some extra tests) have been added to the test
suite.

## Related
Fixes: https://github.com/astral-sh/ruff/issues/18950
2025-07-24 09:45:49 -04:00
Brent Westbrook d13228ab85
Bump 0.12.5 (#19528) 2025-07-24 09:12:50 -04:00
David Peter 9461d3076f
[ty] Rename type_api => ty_extensions (#19523) 2025-07-24 08:24:26 +00:00
UnboundVariable 63d1d332b3
[ty] Added support for "go to references" in ty playground. (#19516)
This PR adds support for "go to references" in the ty playground.

<img width="393" height="168" alt="image"
src="https://github.com/user-attachments/assets/ce3ae1bf-c17c-4510-9f77-20b10f6170c4"
/>

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-23 22:46:42 -07:00
Douglas Creager e0149cd9f3
[ty] Return a tuple spec from the iterator protocol (#19496)
This PR updates our iterator protocol machinery to return a tuple spec
describing the elements that are returned, instead of a type. That
allows us to track heterogeneous iterators more precisely, and
consolidates the logic in unpacking and splatting, which are the two
places where we can take advantage of that more precise information.
(Other iterator consumers, like `for` loops, have to collapse the
iterated elements down to a single type regardless, and we provide a new
helper method on `TupleSpec` to perform that summarization.)
2025-07-23 17:11:44 -04:00
David Peter 2a00eca66b
[ty] Exhaustiveness checking & reachability for `match` statements (#19508)
## Summary

Implements proper reachability analysis and — in effect — exhaustiveness
checking for `match` statements. This allows us to check the following
code without any errors (leads to *"can implicitly return `None`"* on
`main`):

```py
from enum import Enum, auto

class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()

def hex(color: Color) -> str:
    match color:
        case Color.RED:
            return "#ff0000"
        case Color.GREEN:
            return "#00ff00"
        case Color.BLUE:
            return "#0000ff"
```

Note that code like this already worked fine if there was a
`assert_never(color)` statement in a catch-all case, because we would
then consider that `assert_never` call terminal. But now this also works
without the wildcard case. Adding a member to the enum would still lead
to an error here, if that case would not be handled in `hex`.

What needed to happen to support this is a new way of evaluating match
pattern constraints. Previously, we would simply compare the type of the
subject expression against the patterns. For the last case here, the
subject type would still be `Color` and the value type would be
`Literal[Color.BLUE]`, so we would infer an ambiguous truthiness.

Now, before we compare the subject type against the pattern, we first
generate a union type that corresponds to the set of all values that
would have *definitely been matched* by previous patterns. Then, we
build a "narrowed" subject type by computing `subject_type &
~already_matched_type`, and compare *that* against the pattern type. For
the example here, `already_matched_type = Literal[Color.RED] |
Literal[Color.GREEN]`, and so we have a narrowed subject type of `Color
& ~(Literal[Color.RED] | Literal[Color.GREEN]) = Literal[Color.BLUE]`,
which allows us to infer a reachability of `AlwaysTrue`.

<details>

<summary>A note on negated reachability constraints</summary>

It might seem that we now perform duplicate work, because we also record
*negated* reachability constraints. But that is still important for
cases like the following (and possibly also for more realistic
scenarios):

```py
from typing import Literal

def _(x: int | str):
    match x:
        case None:
            pass # never reachable
        case _:
            y = 1

    y
```

</details>

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

## Test Plan

* I verified that this solves all examples from the linked ticket (the
first example needs a PEP 695 type alias, because we don't support
legacy type aliases yet)
* Verified that the ecosystem changes are all because of removed false
positives
* Updated tests
2025-07-23 22:45:45 +02:00
David Peter 3d17897c02
[ty] Fix narrowing and reachability of class patterns with arguments (#19512)
## Summary

I noticed that our type narrowing and reachability analysis was
incorrect for class patterns that are not irrefutable. The test cases
below compare the old and the new behavior:

```py
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

class Other: ...

def _(target: Point):
    y = 1

    match target:
        case Point(0, 0):
            y = 2
        case Point(x=0, y=1):
            y = 3
        case Point(x=1, y=0):
            y = 4
    
    reveal_type(y)  # revealed: Literal[1, 2, 3, 4]    (previously: Literal[2])


def _(target: Point | Other):
    match target:
        case Point(0, 0):
            reveal_type(target)  # revealed: Point
        case Point(x=0, y=1):
            reveal_type(target)  # revealed: Point    (previously: Never)
        case Point(x=1, y=0):
            reveal_type(target)  # revealed: Point    (previously: Never)
        case Other():
            reveal_type(target)  # revealed: Other    (previously: Other & ~Point)
```

## Test Plan

New Markdown test
2025-07-23 18:45:03 +02:00
UnboundVariable fa1df4cedc
[ty] Implemented partial support for "find references" language server feature. (#19475)
This PR adds basic support for the "find all references" language server feature.

---------

Co-authored-by: UnboundVariable <unbound@gmail.com>
2025-07-23 09:16:22 -07:00
chiri 89258f1938
[`flake8-use-pathlib`] Add autofix for `PTH101`, `PTH104`, `PTH105`, `PTH121` (#19404)
## Summary

Part of https://github.com/astral-sh/ruff/issues/2331

## Test Plan

`cargo nextest run flake8_use_pathlib`
2025-07-23 12:13:43 -04:00
हिमांशु 1dcef1a011
[`perflint`] Parenthesize generator expressions (`PERF401`) (#19325)
## Summary
closes #19204 

## Test Plan
1. test case is added in dedicated file
2. locally tested the code manually

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
Co-authored-by: CodeMan62 <sharmahimanshu150082007@gmail.com>
2025-07-23 12:08:15 -04:00
Dan Parizher ba629fe262
[`pep8-naming`] Fix `N802` false positives for `CGIHTTPRequestHandler` and `SimpleHTTPRequestHandler` (#19432)
## Summary

Fixes #19422
2025-07-23 12:04:11 -04:00
frank bb3a05f92b
[`pylint`] Handle empty comments after line continuation (`PLR2044`) (#19405)
fixes #19326
2025-07-23 11:56:49 -04:00
Brent Westbrook 4daf59e5e7
Move concise diagnostic rendering to `ruff_db` (#19398)
## Summary

This PR moves most of the work of rendering concise diagnostics in Ruff
into `ruff_db`, where the code is shared with ty. To accomplish this
without breaking backwards compatibility in Ruff, there are two main
changes on the `ruff_db`/ty side:
- Added the logic from Ruff for remapping notebook line numbers to cells
- Reordered the fields in the diagnostic to match Ruff and rustc
  ```text
  # old
error[invalid-assignment] try.py:3:1: Object of type `Literal[1]` is not
assignable to `str`
  # new
try.py:3:1: error[invalid-assignment]: Object of type `Literal[1]` is
not assignable to `str`
  ```

I don't think the notebook change failed any tests on its own, and only
a handful of snaphots changed in ty after reordering the fields, but
this will obviously affect any other uses of the concise format, outside
of tests, too.

The other big change should only affect Ruff:

- Added three new `DisplayDiagnosticConfig` options
Micha and I hoped that we could get by with one option
(`hide_severity`), but Ruff also toggles `show_fix_status` itself,
independently (there are cases where we want neither severity nor the
fix status), and during the implementation I realized we also needed
access to an `Applicability`. The main goal here is to suppress the
severity (`error` above) because ruff only uses the `error` severity and
to use the secondary/noqa code instead of the line name
(`invalid-assignment` above).
  ```text
  # ty - same as "new" above
try.py:3:1: error[invalid-assignment]: Object of type `Literal[1]` is
not assignable to `str`
  # ruff
try.py:3:1: RUF123 [*] Object of type `Literal[1]` is not assignable to
`str`
  ```

This part of the concise diagnostic is actually shared with the `full`
output format in Ruff, but with the settings above, there are no
snapshot changes to either format.

## Test Plan

Existing tests with the handful of updates mentioned above, as well as
some new tests in the `concise` module.

Also this PR. Swapping the fields might have broken mypy_primer, unless
it occasionally times out on its own.

I also ran this script in the root of my Ruff checkout, which also has
CPython in it:

```shell
flags=(--isolated --no-cache --no-respect-gitignore --output-format concise .)
diff <(target/release/ruff check ${flags[@]} 2> /dev/null) \
     <(ruff check ${flags[@]} 2> /dev/null)
```

This yielded an expected diff due to some t-string error changes on main
since 0.12.4:
```diff
33622c33622
< crates/ruff_python_parser/resources/inline/err/f_string_lambda_without_parentheses.py:1:15: SyntaxError: Expected an element of or the end of the f-string
---
> crates/ruff_python_parser/resources/inline/err/f_string_lambda_without_parentheses.py:1:15: SyntaxError: Expected an f-string or t-string element or the end of the f-string or t-string
33742c33742
< crates/ruff_python_parser/resources/inline/err/implicitly_concatenated_unterminated_string_multiline.py:4:1: SyntaxError: Expected an element of or the end of the f-string
---
> crates/ruff_python_parser/resources/inline/err/implicitly_concatenated_unterminated_string_multiline.py:4:1: SyntaxError: Expected an f-string or t-string element or the end of the f-string or t-string
34131c34131
< crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py:2:15: SyntaxError: Expected an element of or the end of the t-string
---
> crates/ruff_python_parser/resources/inline/err/t_string_lambda_without_parentheses.py:2:15: SyntaxError: Expected an f-string or t-string element or the end of the f-string or t-string
```

So modulo color, the results are identical on 38,186 errors in our test
suite and CPython 3.10.

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-07-23 11:43:32 -04:00
Jack O'Connor 88bd82938f
[ty] highlight the argument in `static_assert` error messages (#19426)
Closes https://github.com/astral-sh/ty/issues/209.

Before:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
```

After:
```
error[static-assert-error]: Static assertion error: custom message
 --> test.py:2:1
  |
1 | from ty_extensions import static_assert
2 | static_assert(3 > 4, "custom message")
  | ^^^^^^^^^^^^^^-----^^^^^^^^^^^^^^^^^^^
  |               |
  |               Inferred type of argument is `Literal[False]`
  |
```
2025-07-23 08:24:12 -07:00
David Peter 5a55bab3f3
[ty] Infer single-valuedness for enums based on `int`/`str` (#19510)
## Summary

We previously didn't recognize `Literal[Color.RED]` as single-valued, if
the enum also derived from `str` or `int`:
```py
from enum import Enum

class Color(str, Enum):
    RED = "red"
    GREEN = "green"
    BLUE = "blue"

def _(color: Color):
    if color == Color.RED:
        reveal_type(color)  # previously: Color, now: Literal[Color.RED]
```

The reason for that was that `int` and `str` have "custom" `__eq__` and
`__ne__` implementations that return `bool`. We do not treat enum
literals from classes with custom `__eq__` and `__ne__` implementations
as single-valued, but of course we know that `int.__eq__` and
`str.__eq__` are well-behaved.

## Test Plan

New Markdown tests.
2025-07-23 15:55:42 +02:00
Andrew Gallant cc5885e564 [ty] Restructure submodule query around `File` dependency
This makes caching of submodules independent of whether `Module`
is itself a Salsa ingredient. In fact, this makes the work done in
the prior commit superfluous. But we're possibly keeping it as an
ingredient for now since it's a bit of a tedious change and we might
need it in the near future.

Ref https://github.com/astral-sh/ruff/pull/19495#pullrequestreview-3045736715
2025-07-23 09:46:40 -04:00
Andrew Gallant 4573a0f6a0 [ty] Make `Module` a Salsa ingredient
We want to write queries that depend on `Module` for caching. While it
seems it can be done without making `Module` an ingredient, it seems it
is best practice to do so.

[best practice to do so]: https://github.com/astral-sh/ruff/pull/19408#discussion_r2215867301
2025-07-23 09:46:40 -04:00
David Peter 905b9d7f51
[ty] Reachability analysis for `isinstance(…)` branches (#19503)
## Summary

Add more precise type inference for a limited set of `isinstance(…)`
calls, i.e. return `Literal[True]` if we can be sure that this is the
correct result. This improves exhaustiveness checking / reachability
analysis for if-elif-else chains with `isinstance` checks. For example:

```py
def is_number(x: int | str) -> bool:  # no "can implicitly return `None` error here anymore
    if isinstance(x, int):
        return True
    elif isinstance(x, str):
        return False

    # code here is now detected as being unreachable
```

This PR also adds a new test suite for exhaustiveness checking.

## Test Plan

New Markdown tests

### Ecosystem analysis

The removed diagnostics look good. There's [one
case](f52c4f1afd/torchvision/io/video_reader.py (L125-L143))
where a "true positive" is removed in unreachable code. `src` is
annotated as being of type `str`, but there is an `elif isinstance(src,
bytes)` branch, which we now detect as unreachable. And so the
diagnostic inside that branch is silenced. I don't think this is a
problem, especially once we have a "graying out" feature, or a lint that
warns about unreachable code.
2025-07-23 13:06:30 +02:00
David Peter b605c3e232
[ty] Normalize single-member enums to their instance type (#19502)
## Summary

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

Labeling this as `internal`, since we haven't released the
enum-expansion feature.

## Test Plan

New Markdown tests
2025-07-23 10:14:20 +02:00
Micha Reiser c281891b5c
[ty] Invert `ty_ide` and `ty_project` dependency (#19501) 2025-07-23 07:37:46 +00:00
Dhruv Manilawala 53d795da67
[ty] Implement mock language server for testing (#19391)
## Summary

Closes: astral-sh/ty#88

This PR implements an initial version of a mock language server that can
be used to write e2e tests using the real server running in the
background.

The way it works is that you'd use the `TestServerBuilder` to help
construct the `TestServer` with the setup data. This could be the
workspace folders, populating the file and it's content in the memory
file system, setting the right client capabilities to make the server
respond correctly, etc. This can be expanded as we write more test
cases.

There are still a few things to follow-up on:
- ~In the `Drop` implementation, we should assert that there are no
pending notification, request and responses from the server that the
test code hasn't handled yet~ Implemented in [`afd1f82`
(#19391)](afd1f82bde)
- Reduce the setup boilerplate in any way we can
- Improve the final assertion, currently I'm just snapshotting the final
output

## Test Plan

Written a few test cases.
2025-07-23 12:26:58 +05:30
David Peter 385d6fa608
[ty] Detect enums if metaclass is a subtype of EnumType/EnumMeta (#19481)
## Summary

This PR implements the following section from the [typing spec on
enums](https://typing.python.org/en/latest/spec/enums.html#enum-definition):

> Enum classes can also be defined using a subclass of `enum.Enum` **or
any class that uses `enum.EnumType` (or a subclass thereof) as a
metaclass**. Note that `enum.EnumType` was named `enum.EnumMeta` prior
to Python 3.11.

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

## Test Plan

New Markdown tests
2025-07-23 08:46:51 +02:00
Jack O'Connor ba070bb6d5
[ty] perform type narrowing for places marked `global` too (#19381)
Fixes https://github.com/astral-sh/ty/issues/311.
2025-07-22 16:42:10 -07:00
Micha Reiser dc10ab81bd
[ty] Use `ThinVec` for sub segments in `PlaceExpr` (#19470) 2025-07-22 20:39:39 +02:00
Douglas Creager 7673d46b71
[ty] Splat variadic arguments into parameter list (#18996)
This PR updates our call binding logic to handle splatted arguments.

Complicating matters is that we have separated call bind analysis into
two phases: parameter matching and type checking. Parameter matching
looks at the arity of the function signature and call site, and assigns
arguments to parameters. Importantly, we don't yet know the type of each
argument! This is needed so that we can decide whether to infer the type
of each argument as a type form or value form, depending on the
requirements of the parameter that the argument was matched to.

This is an issue when splatting an argument, since we need to know how
many elements the splatted argument contains to know how many positional
parameters to match it against. And to know how many elements the
splatted argument has, we need to know its type.

To get around this, we now make the assumption that splatted arguments
can only be used with value-form parameters. (If you end up splatting an
argument into a type-form parameter, we will silently pass in its
value-form type instead.) That allows us to preemptively infer the
(value-form) type of any splatted argument, so that we have its arity
available during parameter matching. We defer inference of non-splatted
arguments until after parameter matching has finished, as before.

We reuse a lot of the new tuple machinery to make this happen — in
particular resizing the tuple spec representing the number of arguments
passed in with the tuple length representing the number of parameters
the splat was matched with.

This work also shows that we might need to change how we are performing
argument expansion during overload resolution. At the moment, when we
expand parameters, we assume that each argument will still be matched to
the same parameters as before, and only retry the type-checking phase.
With splatted arguments, this is no longer the case, since the inferred
arity of each union element might be different than the arity of the
union as a whole, which can affect how many parameters the splatted
argument is matched to. See the regression test case in
`mdtest/call/function.md` for more details.
2025-07-22 14:33:08 -04:00
frank 9d5ecacdc5
[`flake8-pyi`] Skip fix if all `Union` members are `None` (`PYI016`) (#19416)
patches #19403

---------

Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com>
2025-07-22 17:03:14 +00:00
David Peter 64e5780037
[ty] Consistent use of American english (in rules) (#19488)
## Summary

Just noticed this as a minor inconsistency in our rules, and had Claude
do a few more automated replacements.
2025-07-22 16:10:38 +02:00
David Peter da8aa6a631
[ty] Support iterating over enums (#19486)
## Summary

Infer the correct type in a scenario like this:

```py
class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

for color in Color:
    reveal_type(color)  # revealed: Color
```

We should eventually support this out-of-the-box when
https://github.com/astral-sh/ty/issues/501 is implemented. For this
reason, @AlexWaygood would prefer to keep things as they are (we
currently infer `Unknown`, so false positives seem unlikely). But it
seemed relatively easy to support, so I'm opening this for discussion.

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

## Test Plan

Adapted existing test.

## Ecosystem analysis

```diff
- warning[unused-ignore-comment] rotkehlchen/chain/aggregator.py:591:82: Unused blanket `type: ignore` directive
```

This `unused-ignore-comment` goes away due to a new true positive.
2025-07-22 16:09:28 +02:00
David Peter ee69d38000
Fix panic for illegal `Literal[…]` annotations with inner subscript expressions (#19489)
## Summary

Fixes pull-types panics for illegal annotations like
`Literal[object[index]]`.

Originally reported by @AlexWaygood

## Test Plan

* Verified that this caused panics in the playground, when typing (and
potentially hovering over) `x: Literal[obj[0]]`.
* Added a regression test
2025-07-22 14:07:20 +00:00
Brent Westbrook fd335eb8b7
Move fix suggestion to subdiagnostic (#19464)
Summary
--

This PR tweaks Ruff's internal usage of the new diagnostic model to more
closely
match the intended use, as I understand it. Specifically, it moves the
fix/help
suggestion from the primary annotation's message to a subdiagnostic. In
turn, it
adds the secondary/noqa code as the new primary annotation message. As
shown in
the new `ruff_db` tests, this more closely mirrors Ruff's current
diagnostic
output.

I also added `Severity::Help` to render the fix suggestion with a
`help:` prefix
instead of `info:`.

These changes don't have any external impact now but should help a bit
with #19415.

Test Plan
--

New full output format tests in `ruff_db`

Rendered Diagnostics
--

Full diagnostic output from `annotate-snippets` in this PR:

``` 
error[unused-import]: `os` imported but unused
  --> fib.py:1:8
   |
 1 | import os
   |        ^^
   |
 help: Remove unused import: `os`
```

Current Ruff output for the same code:

```
fib.py:1:8: F401 [*] `os` imported but unused
  |
1 | import os
  |        ^^ F401
  |
  = help: Remove unused import: `os`
```

Proposed final output after #19415:

``` 
F401 [*] `os` imported but unused
  --> fib.py:1:8
   |
 1 | import os
   |        ^^
   |
 help: Remove unused import: `os`
```

These are slightly updated from
https://github.com/astral-sh/ruff/pull/19464#issuecomment-3097377634
below to remove the extra noqa codes in the primary annotation messages
for the first and third cases.
2025-07-22 10:03:58 -04:00
Aria Desires c82fa94e0a
[ty] Implement non-stdlib stub mapping for classes and functions (#19471)
This implements mapping of definitions in stubs to definitions in the
"real" implementation using the approach described in
https://github.com/astral-sh/ty/issues/788#issuecomment-3097000287

I've tested this with goto-definition in vscode with code that uses
`colorama` and `types-colorama`.

Notably this implementation does not add support for stub-mapping stdlib
modules, which can be done as an essentially orthogonal followup in the
implementation of `resolve_real_module`.

Part of https://github.com/astral-sh/ty/issues/788
2025-07-22 12:42:55 +00:00
David Peter 6d4687c9af
[ty] Disallow illegal uses of `ClassVar` (#19483)
## Summary

It was faster to implement this then to write the ticket: Disallow
`ClassVar` annotations almost everywhere outside of class body scopes.

## Test Plan

New Markdown tests
2025-07-22 14:21:29 +02:00
David Peter 9180cd094d
[ty] Disallow `Final` in function parameter/return-type annotations (#19480)
## Summary

Disallow `Final` in function parameter- and return-type annotations.

[Typing
spec](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final):

> `Final` may only be used in assignments or variable annotations. Using
it in any other position is an error. In particular, `Final` can’t be
used in annotations for function arguments

## Test Plan

Updated MD test
2025-07-22 13:15:19 +02:00
David Peter 9d98a66f65
[ty] Extend `Final` test suite (#19476)
## Summary

Restructures and cleans up the `typing.Final` test suite. Also adds a
few more tests with TODOs based on the [typing spec for
`typing.Final`](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final).
2025-07-22 12:06:47 +02:00
David Peter cb60ecef6b
[ty] Minor change to diagnostic message for invalid Literal uses (#19482) 2025-07-22 11:42:12 +02:00