Commit Graph
16526 Commits
Author SHA1 Message Date
Micha Reiser e87ddc00ea [ty] Move Python version source out of Program (#27434)
## Summary

`PythonVersionWithSource` contains from where ty derived the
`PythonVersion`. If the Python version comes from the project's
`pyproject.toml`, then the source includes the configuration file name
and the text range of the `requires-python` or `python-version`
key-value pair. Any change to that `pyproject.toml` would result in a
new and different `PythonVersionWithSource`, which, in turn, results in
a distinct interned `Program`. This results in invalidating every single
type inference query (more accurately, we now get a new instance of each
type inference query, but we loose all cached results).

Storing `PythonVersionWithSource` also has the downside that it prevents
scripts that otherwise have identical `Program` settings to share the
same `Program`, only because their `python-version` comes from different
script metadata blocks.

This PR moves `PythonVersionWithSource` out of `Program`. `Program`
should only contain information that changes type inference.
`PythonVersionWithSource` does not. `Db` now has a new
`python_version_with_source` method that, given a file, returns the
source for it (A script can return a different source than a project
file).

## Test Plan

Testing: Semantic, project, core, server, Markdown, and corpus tests
pass; repository hooks pass.
2026-08-04 08:51:18 +02:00
Micha Reiser aa5b04ad92 [ty] Support different type inference settings in a single Salsa DB (#27152)
## Summary

This is the last PR that adds support for type checking multiple
programs with different Python Version, search paths, and platform using
a single Salsa DB.


This PR changes `Program` from a singleton input to an interned struct.
`ProgramFile` changes from `(File, ResolverEnvironment)` to `(File,
Program)`.

Changing `Program` from an input to an interned means that it now has a
`'db` lifetime. This requires changing how we construct and store
`Program`. That's why some of the db initialization code changes.

I doubt that it will be noticeable. But this should speed up mdtests
that contain tests with different Python versions (e.g. Py312, Py311,
Py312, Py311), because we now reuse parsed modules, and even type
inference results because the inference is now keyed by Program (instead
of updating an existing Program)

## Memory regression

I had codex look into the CodSpeed reported memory regression. The
reason is that `Program` now being an interned and being initialized
later changes the assigned `File` ids, which in turn changes in which
order we iterate over the project files. And this change the max peak
memory usage. It can also change the usage reported on the memory usage
report (we enter fixpoint cycles from different heads, etc).

The changes in the memory report are mainly from the new
`ProgramFile::python_version` (needed to mitigate a larger regression,
but we can reconsider), and that `Project` now stores `ProgramSettings`.
2026-08-04 08:51:18 +02:00
Micha Reiser 95734a549e [ty] Make module resolution environment-aware (#27128)
## Summary

This PR continues the refactor to make type inference and module
resolution support scripts that may have different settings than the
main project.

This PR introduces:

* `ResolverEnvironment(SearchPaths, PythonVersion)`: Uniquely identifies
the settings used to resolve a module
* `ResolverFile(File, ResolverEnvironment)`: A file with its
`ResolverEnvironment`. Used for desperate module resolution and
`module_to_file` (and all other places where the operation doesn't
depend on type-inference specific settings).


This PR also `ProgramFile` and updates the `Program` alias to point to
`ResolverEnvironment`. These changes are in preparation to reduce the
diff of the third PR (we don't want to rewrite all `python_file` call
sites to `resolver_file` to then rename them to `program_file`).

## Memory regression

This PR changes `Program` from `PythonVersion` (u16) to
`ResolverEnvironment` (u64). This increases the size of interned structs
and interned query arguments that contain `Program`. Memory usage is
still lower than before we made db `Program` aware (the base PR reduced
memory usage by about 1%).

## Test Plan

Testing: Ran workspace checks, resolver/core/semantic/IDE tests, and
repository hooks, including cross-version inference coverage.
2026-08-04 08:51:17 +02:00
Charlie Marsh 7fd30bb81c [ty] Check property compatibility when matching class objects to protocols (#27402)
## Summary

When matching a class object against a protocol, we previously treated
any `property` descriptor as an unconditional match for a protocol
property. This reduced compatibility to a name-only check even though
accessing an instance property on the class exposes the descriptor, not
the value returned by its getter:

```py
class HasValue(Protocol):
    @property
    def value(self) -> str: ...

class Value:
    @property
    def value(self) -> str: ...

target: HasValue = Value  # error
```

This removes the stale shortcut so the class attribute and protocol
member are compared using the normal type relation.

That shortcut also interfered with generic inference for unions, like in
NumPy's `_DTypeLike[T]`, where it allowed a scalar class such as
`np.int64` to match `_SupportsDType[dtype[T]]` without constraining `T`,
overriding the useful `type[T]` match and producing `Unknown`. With
normal property compatibility, the class-object branch binds `T`, so
explicit dtypes remain specialized through downstream calls:

```py
values = np.array([0, 1, 2], dtype=np.int64)
reveal_type(values)  # ndarray[..., dtype[signedinteger[_64Bit]]]
reveal_type(np.interp(values, values, values))  # ndarray[..., dtype[float64]]
```

Closes https://github.com/astral-sh/ty/issues/3199.
2026-08-04 01:16:29 +00:00
Avasam 64a4f10853 Add pywin32 to Ruff ecosystem checks (#27437)
<!--
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?
- Does this PR follow our AI policy
(https://github.com/astral-sh/.github/blob/main/AI_POLICY.md)?
-->

## Summary

As a pywin32 maintainer and Ruff user, I'd like to add pywin32 to the
ecosystem checks.

I think that pywin32 would be a good fit given its age, size,
popularity, and importance to the Python on Windows ecosystem. As well
as my efforts in the past 3 years modernizing its Python codebase.

Most of the Ruff issues / requests I opened have come from using it in
setuptools and pywin32.

## Test Plan

N/A ?
2026-08-03 20:17:06 -04:00
Charlie Marsh f70a1a3ace [ty] Hide stub-only helpers from implicit builtin lookup (#27423)
## Summary

Typeshed's `builtins.pyi` defines private type variables, aliases, and
protocols that do not exist in the runtime builtins namespace.
Previously, implicit builtin fallback treated those stub-only helpers as
ordinary builtins, so undefined names such as `_T_co` silently resolved
(this should fail -- `_T_co` doesn't exist!):

```python
class SupportsNext:
    def __next__(self) -> _T_co:  # error: [unresolved-reference]
        raise NotImplementedError
```

This PR adds a shared `exists_at_runtime()` predicate for stub
definitions and applies it consistently to implicit builtin resolution.
Specifically, it excludes private type variables, aliases, and
type-checking-only definitions, while preserving explicit imports, real
private runtime builtins, and project-level `__builtins__.pyi`
overrides.

As a result, like mypy, Pyright, and Pyrefly, we reject implicit
references to private typing-only builtin helpers. And like mypy and
Pyrefly (but unlike Pyright), we still allow explicit imports, like
`from builtins import _T_co`.

Closes https://github.com/astral-sh/ty/issues/1483.
2026-08-03 12:55:55 -04:00
Charlie Marsh acc1b0f263 [ty] Reject out-of-scope ParamSpec components (#27378)
## Summary

Make `P.args` and `P.kwargs` refer to an existing `ParamSpec` binding
instead of creating one themselves.

This is now rejected:

```python
P = ParamSpec("P")

def f(*args: P.args, **kwargs: P.kwargs) -> None: ...
```

`P` must be bound by another parameter or a visible enclosing scope. A
`ParamSpec` attached to an enclosing factory's returned callable remains
visible inside the factory, so this is still valid:

```python
def factory() -> Callable[P, int]:
    def inner(*args: P.args, **kwargs: P.kwargs) -> int:
        return 1

    return inner
```

Nested classes still hide bindings from outer classes.
2026-08-03 11:34:12 -04:00
Alex Waygood 6ffd512049 [ty] Move Unknown into ty_extensions._internal (#27430) 2026-08-03 13:03:38 +01:00
David Peter a24a1dd3b3 [ty] Recognize Pydantic on extra search paths (#27429)
## Summary

Recognize `pydantic` as being third-party code, and therefore activate
special Pydantic support, even if the package appears in `extra-paths`.

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

## Test plan

Added a regression test
2026-08-03 04:46:03 -07:00
David Peter cd138a3289 [ty] Document inference behavior of unannotated parameters with default values (#24943)
## Summary

In this PR, I explored the proposal in
https://github.com/astral-sh/ty/issues/3251. The idea was to treat a
function like `def f(x="foo"): ...` as if the `x` parameter was declared
as `str`. This is what pyright does, and would follow what we do for
unannotated class attributes with default values. However, implementing
this showed a huge ecosystem impact of 8700 new diagnostics.

I tried various things to improve this. For example, I promoted `None`
to `Unknown | None` because it seems unreasonable to assume that you
only ever want callers to pass in `None`. Then there's a lot of code
that takes `x=()` as a default value and wants callers to be able to
pass in arbitrary tuples. I tried patching that by promoting `tuple[()]
-> tuple[Unknown, ...]`.

But the biggest problem is with functions that take a default int value
(e.g. `x=0`), but want to allow their callers to pass in float values. I
tried promoting the type of `x` from `int` to `float`/`complex`, but
that leads to the opposite problem: now the function body of *every*
function that takes `x=0` must also handle the possibility that `x` is
`float`/`complex`.

Ultimately, I think a scenario like this:
```py
def some_computation(..., tolerance=0): ...

some_computation(..., tolerance=1e-3)
```
is just far more common than the corresponding scenario with class
attributes.

I think it's unfortunate that our behavior is inconsistent with what we
do for class attributes, but I don't think it's worth introducing ~8000
diagnostics (no variant that I tried went below that limit). The
corresponding PR for attributes introduced an order of magnitude fewer
new diagnostics (https://github.com/astral-sh/ruff/pull/24531).

So in this PR, all I do is to document the current behavior.

closes https://github.com/astral-sh/ty/issues/3251
2026-08-03 03:16:55 -07:00
Micha Reiser f78d51606f [ty] Make parsed modules Python-version aware (#26856)
## Summary

This is the first PR out of 3. It extends `ty_python_semantic` and lower
crates to support checking files that use different Python versions
while using a single Salsa db.

The motivation for supporting different Python versions is that scrolls
need to be checked independently. Initially, I planned to implement
scroll support in a similar way to how we support multiple workspace
folders in VS Code by using multiple dbs, but it turned out, that
they're different enough. The main difference is that scrolls are
checked as part of their enclosing project, and the diagnostics from
scrolls are merged with the diagnostics from the project. If we used
separate scroll dbs, then the scroll diagnostics would use `File`s
pointing into the scroll db and all other diagnostics would use `File`s
pointing into the main db. There's nothing that would allow us to tell
to which db any such `File`s belonged, and `File`s from different dbs
can even share the same value. There's also no lifetime that binds them
to one particular db because they're inputs. We could have decided to
rewrite the `Diagnostic`s for scrolls and convert the scroll db `File`s
to project db `File`s, but that felt very brittle. The other
disadvantage was that it became very difficult to tell which query is
cached because you now also need to reason about whether the current
query is cached for a specific argument and db (and not just the
argument).

This is why I decided to explore whether we could support checking files
with different Python version, search paths, and platforms, and this PR
is the first step towards this. It adds support for checking files with
different Python versions. Specifically, this PR removes the
`db.python_version` from `ruff_db::Db` and lifts it up to
`ty_project::Db`, proving that no semantic crate depends on it.

The first step is to make `parsed_module` Python version aware. That is,
instead of calling `db.python_version`, parametrize the query by file
and Python version. For this, I introduced a new `PythonFile` which is a
`(File, PythonVersion)` tuple. We now attach this `PythonFile` to
`Scope` (instead of `File`), because `::node` accessors need it to
retrieve their node. That in turn required `semantic_index` to take a
`PythonFile` instead of `File` as well. This was a mostly smooth ride.

Supporting multiple Python versions in the module resolver required
adding a `PythonVersion` argument to most queries. The next PR will
replace the `PythonVersion` with a new `ResolverSettings` (or similar
name) interned struct that wraps both Python version and search paths.
This was also mostly straightforward.

The hard part was to remove all `db.python_version` calls from type
inference and type operations. Today, we call `db.python_version` where
we need it, but we can't do this anymore. Instead, we have to pass the
Python version down to each function that needs it.

Now, this is not true for all functions. Functions that have access to a
scope (via `Definition`, `Expression` or similar) could retrieve the
Python version from there (because `Scope` stores a `PythonFile`). I
decided against that and, instead established the following policy:

* Explicitly pass the Python version to all semantic functions. 
* The only exception to this are Salsa queries that have exactly one
argument other than `db`. For those, retrieving the Python version from
their `Scope` is allowed for performance reasons.

Ultimately what that means: The context should be initialized at the
query boundary and then not change, unless we cross into another Salsa
query (but assert that the python version does not change).


There are two motivations for this:

* I think it's easier to reason about what Python version a type
operation uses if there's a consistent way of retrieving it (always as
argument)
* At query boundaries, we can assert that the explicitly passed Python
version and the `Scope`'s Python version are identical, before calling
into the query (debug assert).



However, supporting different Python versions now requires that we
parametrize all type operations with the Python version under which the
operation should be performed (in the future, this will be Python
version, search paths, and platforms).

Now, passing `db` and `PythonVersion` as separate arguments not only
results in a huge diff (add another 7k changes on top of the current
diff), is cumbersome, it also is slower. That's why I this PR introduces
a new `SemanticContext` that wraps a `db` and a `PythonVersion` (Happy
to discuss names). The variable is consistently referred to as `ctx` to
reduce the diff (using `context` makes it more likely that lines break).


## Next steps

* Change `SemanticContext` to store a `db` and `SearchPathSettings` to
support different search paths
* After that, change `SemanticContext` to store a `db` and a
`ProgramFile` (which is a `(File, Program)` tuple.
* Use the new infrastructure for scroll support

## Performance

There's currently a 2-3% walltime regression. Codex reports a lower
local regression.


There are some options that we can explore if we think this regression
is too big. However, it would make the model a little more muddy. The
most promising is probably to have two versions of very hot queries.
E.g. `is_redundant_with` could have a `is_redundant_with_project` and
`is_redundant_with_script` query. `is_redundant_with_project` would use
`db.python_version` internally (yes, we'd have to add that back). I
don't like that. Not only does it reintroduce `db.python_version` and
make it very easy that we use it somewhere where we should not.


## Review

I, honestly, tried to split this PR into smaller, reviewable commits.
But I don't think I succeeded. But I also don't think it makes sense to
review this commit line by line. I'm sure there are places where we
still pass `db` where we should pass a `ctx`. I'm sure there are some
call sites where we should retrieve the `python_version` in a different
way. But I'm not sure it's worth finding all these places in a review.
Instead, I'd focus on the overall change. What's your reaction on
`SemanticContext`? That we now need to pass it everywhere? What about my
policy that we use a single `SemanticContext` within a query? Should we
relax this constraint, so that `SemanticIndex` must be passed in fewer
places, if the `PythonVersion` can be retrieved at the relevant call
site (because it has `Scope`)? The downside of that is that it becomes
more difficult to assert that we don't mix programs in a single
inference, but is this something we should really be concerned about? Do
you think the performance regression is to big for this feature?

## Notes

* Make associated tracked methods that don't take `python_version`
normal methods with an inner query instead. The method should take a
`python_version`, and we assert (before calling the query), that the
target's (self's) `python_version` matches the passed python version
2026-08-03 09:46:10 +02:00
Andrej730 d3baae55bd Use LinterSettings builder methods in tests (#27418) 2026-08-03 09:17:12 +02:00
David Peter d3544b558f [ty] Revert "Remove bespoke inference VecMap iterator" (#27428)
Reverts #27362.
2026-08-03 07:16:40 +00:00
NiyutaandMicha Reiser da17b77414 [ty] Trigger signature help after completing callable with parentheses (#27084)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-08-03 09:06:16 +02:00
Alex Waygood 2fa7c67620 [ty] Prioritize readable mdtest organization in AGENTS.md (#27424)
## Summary

Clarify that mdtest document structure and readability take precedence
over avoiding duplicated setup.

Add an existing-section suitability check: its heading and introductory
prose must describe the new scenario; otherwise, create a separate
section even when that requires repeating a small fixture.
2026-08-02 22:34:56 +01:00
Alex Waygood 299a22766f [ty] Improve error context for incompatible callable signatures (#27422)
## Summary

We had a lot of branches in `signatures.rs` where we were failing to add
error context, which often made our diagnostics less comprehensible than
they could have been. This PR adds context to lots of these branches,
and adds snapshots to cover the new code paths.

There are still lots of `return self.never();` branches, even on this PR
branch, that don't have any error context attached to them. I was too
scared to touch the `ParamSpec`- and `TypeVarTuple`-specific branches in
this PR 🙈 so left them out of scope for now.
2026-08-02 22:32:53 +01:00
BitWeaverandCharlie Marsh e31dffc993 [ty] Detect __aenter__ and __aexit__ that do not return awaitables (#27414)
## Summary

`async with` awaits whatever `__aenter__` and `__aexit__` return, so a
method that is callable but returns a non-awaitable still fails at
runtime:

```python
class C:
    def __aenter__(self) -> int:
        return 0

    async def __aexit__(self, exc_type, exc, tb) -> None: ...

async def f() -> None:
    async with C() as x:  # TypeError: object int can't be used in 'await' expression
        ...
```

ty accepted both of these. `try_enter_with_mode` awaited the
`__aenter__` return type with
`try_await(db).unwrap_or(Type::unknown())`, discarding the failure and
silently binding the target to `Unknown`, and it ignored the `__aexit__`
return type entirely.

Both return types are now awaited in async mode, and a new
`ContextManagerError::NotAwaitable` variant reports the methods at fault
along with the offending return type:

```
error[invalid-context-manager]: Object of type `C` cannot be used with `async with` because `__aenter__` does not return an awaitable
  --> example.py:14:16
   |
14 |     async with C() as x:
   |                ^^^
info: `__aenter__` returns `int`, which is not awaitable
info: Consider declaring the method with `async def`
```

`NonAwaitableMethods` is an enum rather than a collection so that "at
least one method is at fault" cannot be represented as an empty set.

The hint suggesting a switch between `with` and `async with` is
suppressed for this variant. The object does implement the methods for
the mode it was used in, so that suggestion would be misleading.

Sync `with` is unaffected, since it does not await what `__enter__` and
`__exit__` return.

Closes astral-sh/ty#4124

## Test Plan

New cases in `with/async.md` cover a non-awaitable `__aenter__` (with a
snapshot of the full diagnostic), a non-awaitable `__aexit__`, and both
failing together.

Since the risk here is false positives on valid context managers, the
following are asserted to remain clean:

- methods that return an awaitable without being `async def`, spelled as
`Awaitable[T]`, as `Coroutine[...]`, and as a custom class implementing
`__await__`
- union return types where every member is awaitable, and `Never`
- return types that are `Any` or unannotated

A mixed union (`int | Awaitable[int]`) is asserted to still be an error,
since awaiting the non-awaitable arm fails.

Also verified manually, unchanged by this PR: `asyncio.timeout`,
`asyncio.TaskGroup`, `@asynccontextmanager`, protocol-typed context
managers, inherited `__aenter__`/`__aexit__`, and sync `with`. A
possibly-unbound `__aenter__` continues to report the pre-existing
diagnostic rather than double-reporting.

Checks run:

- `cargo test -p ty_python_semantic`: 297 + 14 unit tests and 479
mdtests pass
- `cargo clippy --workspace --all-targets --all-features -- -D
warnings`: clean
- `uv run --only-group dev --locked prek run --files ...`: passes
- `cargo dev generate-all`: no changes (this reuses the existing
`invalid-context-manager` rule)

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2026-08-02 15:01:55 -04:00
Charlie Marsh 2742f956d8 [ty] Remove unused TypeVarVariance bounds (#27408)
## Summary

This PR removes the unused TypeVarVariance bottom and top constructors
identified by Hawk 0.1.11. The variance lattice is implemented through
the internal join operation, and no Ruff or ty code calls these public
constructors.

- 2 unused public methods removed
- 8 lines removed
- 1 file changed
2026-08-01 13:11:12 -04:00
Charlie Marsh c4fb1f4f57 Remove unused ClassMemberBoundness predicates (#27389)
## Summary

This PR removes the unused `ClassMemberBoundness::{is_bound,
is_possibly_unbound}` predicates identified by Hawk 0.1.11. The other
APIs and CFG visualization support considered in #27339 remain
unchanged.

- 2 unused public methods removed
- 10 deletions
- 1 file changed
2026-08-01 16:56:18 +00:00
Charlie Marsh 6002a07c8b Remove unused APIs from ruff_source_file (#27392)
## Summary

This PR removes unused APIs from ruff_source_file identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 31 net lines removed
- 33 deletions and 2 additions
- 1 files changed

This preserves the general-purpose OneIndexed checked arithmetic APIs
called out in review.
2026-08-01 12:54:35 -04:00
Charlie Marsh 1fcdb82be0 Move formatter line terminators into test module (#27384)
## Summary

This PR moves `LINE_TERMINATORS` and its component constants into the
`format_element` test module, as suggested in the review of #27339.
These constants are only used by the newline-normalization tests, so
they do not need to be part of the public API of `ruff_formatter`.

- 1 public constant removed
- 4 additions and 5 deletions
- 1 file changed
2026-08-01 16:45:56 +00:00
Charlie Marsh 64737b2a5c Remove unused APIs from ruff_notebook (#27386)
## Summary

This PR removes unused APIs from ruff_notebook identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 4 net lines removed
- 4 deletions and 0 additions
- 1 files changed
2026-08-01 12:37:51 -04:00
Charlie Marsh ee278f44e0 Restore symmetric public APIs (#27405)
## Summary

This follows up on #27339 based on review feedback.

- Remove internal re-exports instead of retaining them with
`#[expect(unused_imports)]`.
- Restore the symmetric `Edit` and `NoqaCode` accessors to public
visibility.
- Keep `EditOperationKind`'s helper methods private along with their
containing type.

The public accessors are retained as coherent downstream-facing APIs
even when individual methods are not used within the workspace. Hawk
will document those intentional exceptions in the separate
CI/configuration PR.
2026-08-01 12:21:27 -04:00
Andrej730 9ef3f9123e [ruff_python_ast] Fix double visit / missing elif visit (#27404) 2026-08-01 13:53:50 +00:00
github-actions[bot]andtypeshedbot 62e3fb4d3c [ty] Sync vendored typeshed stubs (#27401)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2026-08-01 09:40:45 +01:00
Charlie Marsh adb763deb8 [ty] Remove unused APIs from ty_project (#27395)
## Summary

This PR removes unused APIs from ty_project identified by Hawk 0.1.11.
It is split from #27339 so the removals can be reviewed independently at
the crate boundary.

- 27 net lines removed
- 27 deletions
- 2 files changed
2026-08-01 02:47:23 +00:00
Charlie Marsh 67ecabc3c4 [ty] Remove unused APIs from ty_module_resolver (#27394)
## Summary

This PR removes unused APIs from ty_module_resolver identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 1 net lines removed
- 2 deletions and 1 additions
- 2 files changed
2026-07-31 22:37:55 -04:00
Charlie Marsh 9783303186 Remove unused APIs from ruff_db (#27382)
## Summary

This PR removes unused APIs from ruff_db identified by Hawk 0.1.11. It
is split from #27339 so the removals can be reviewed independently at
the crate boundary.

- 56 net lines removed
- 61 deletions and 5 additions
- 8 files changed
2026-07-31 22:37:36 -04:00
Charlie Marsh 8089b331da [ty] Remove unused APIs from ty_server (#27398)
## Summary

This PR removes unused APIs from ty_server identified by Hawk 0.1.11. It
is split from #27339 so the removals can be reviewed independently at
the crate boundary.

- 4 net lines removed
- 4 deletions and 0 additions
- 1 files changed
2026-07-31 22:37:00 -04:00
Charlie Marsh 103168bd42 Remove unused APIs from ruff_python_stdlib (#27390)
## Summary

This PR removes an unused API from ruff_python_stdlib identified by Hawk
0.1.11. It is split from #27339 so the removal can be reviewed
independently at the crate boundary.

- 6 net lines removed
- 6 deletions
- 1 file changed
2026-08-01 02:32:19 +00:00
Charlie Marsh cc7a77ff9d Remove unused APIs from ruff_linter (#27385)
## Summary

This PR removes unused APIs from ruff_linter identified by Hawk 0.1.11.
It is split from #27339 so the removals can be reviewed independently at
the crate boundary.

- 62 net lines removed
- 64 deletions and 2 additions
- 9 files changed
2026-07-31 22:31:42 -04:00
Charlie Marsh f21e418e5c Remove unused APIs from ruff_diagnostics (#27383)
## Summary

This PR removes unused APIs from ruff_diagnostics identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 11 net lines removed
- 11 deletions and 0 additions
- 1 files changed
2026-07-31 22:28:44 -04:00
Charlie Marsh d6c6b4505a Remove unused APIs from ruff_workspace (#27393)
## Summary

This PR removes unused APIs from ruff_workspace identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 16 net lines removed
- 16 deletions and 0 additions
- 2 files changed
2026-07-31 22:26:49 -04:00
Charlie Marsh e0a0b8d1ca [ty] Remove unused APIs from ty_python_core (#27396)
## Summary

This PR removes unused APIs from ty_python_core identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 10 net lines removed
- 10 deletions and 0 additions
- 2 files changed
2026-07-31 22:26:27 -04:00
Charlie Marsh 589050b1f9 [ty] Remove unused APIs from ty_site_packages (#27399)
## Summary

This PR removes unused APIs from ty_site_packages identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 18 net lines removed
- 18 deletions and 0 additions
- 2 files changed
2026-07-31 22:26:05 -04:00
Charlie Marsh 4d03c621b3 Remove unused APIs from ruff_ranged_value (#27391)
## Summary

This PR removes unused APIs from ruff_ranged_value identified by Hawk
0.1.11. It is split from #27339 so the removals can be reviewed
independently at the crate boundary.

- 10 net lines removed
- 10 deletions and 0 additions
- 1 files changed
2026-07-31 22:24:10 -04:00
Charlie Marsh 6cfeb8aa07 Narrow internal visibility with Hawk (#27339)
## Summary

This PR uses Hawk 0.1.11 to narrow internal declarations to the
visibility they actually need.

Here, I've intentionally _only_ made visibility changes; unused API
removals are split into follow-up PRs. In total, 1,201 declaration
visibility modifiers are narrowed. Public generated formatter APIs and
other declarations whose narrower visibility would activate dead-code or
API-shape lints remain unchanged.
2026-07-31 22:08:13 -04:00
BitWeaverandCharlie Marsh 2272478c06 [ty] Reject ClassVar and Final qualifiers in NamedTuple fields (#27380)
## Summary

`NamedTuple` builds each field's type by passing its annotation through
`typing._type_check`, which rejects bare type qualifiers. A field
annotated with `ClassVar` or `Final` therefore raises `TypeError` as
soon as the class statement executes:

```pycon
>>> from typing import ClassVar, NamedTuple
>>> class Foo(NamedTuple):
...     x: ClassVar[int]
TypeError: typing.ClassVar[int] is not valid as type argument
```

ty accepted both silently. This extends the existing
`invalid-named-tuple` rule to report them.

The obvious place for the check iterates `own_fields`, but that cannot
see either case: it drops `ClassVar` declarations outright, and does not
retain the `Final` qualifier for the fields it does keep. Rather than
duplicate the class-body walk, this extracts it into
`own_annotated_declarations` and adds a second consumer,
`own_annotated_qualifiers`, which reports what the class body literally
says.

Two behaviours worth calling out:

- A field carrying both qualifiers is reported once per qualifier, since
removing just one of them still leaves a class definition that raises
`TypeError`.
- A class that merely inherits from a `NamedTuple` is an ordinary class
at runtime, so it is left alone.

Separately, the two `invalid-named-tuple` reporters for malformed fields
shared their anchoring and primary-annotation logic; that is now
factored into a common `report_invalid_named_tuple_field` helper.

Closes astral-sh/ty#4131

## Test Plan

New mdtests in `named_tuple.md` cover both qualifiers, the unsubscripted
form (`x: ClassVar`), the inheritance non-case, and a snapshot of the
rendered diagnostic.

- `cargo test -p ty_python_semantic`: 297 + 14 unit tests and 479
mdtests pass
- `cargo clippy --workspace --all-targets --all-features -- -D
warnings`: clean
- `uv run --only-group dev --locked prek run --files ...`: passes
- `cargo dev generate-all` re-run for the rule documentation change

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2026-07-31 18:41:57 -04:00
Charlie Marsh 9e5424f0c4 [ty] Preserve TypeVarTuple context during Generic recovery (#27381)
## Summary

Prior to this change, we recovered an invalid `Generic[Shape]`
subscription as `Unknown`. That lost the class's generic context and
caused a cascading diagnostic for a correctly unpacked use:

```py
Shape = TypeVarTuple("Shape")

class ClassA(Generic[Shape]):  # invalid-generic-class
    values: tuple[*Shape]  # Previously: unbound-type-variable
```

We now preserve the bound `TypeVarTuple` in the recovery type while
still reporting the invalid bare argument. This removes only the
spurious `unbound-type-variable` diagnostic and makes
`generics_typevartuple_basic.py` fully conformant.
2026-07-31 16:56:59 -04:00
Charlie Marsh 8430a098fb [ty] Reject specializing non-generic subclasses (#27377)
## Summary

Reject specialization of a subclass after all generic parameters in its
bases have been specialized, including by defaults.

```python
T = TypeVar("T")
DefaultT = TypeVar("DefaultT", default=str)

class Base(Generic[T, DefaultT]): ...
class Child(Base[int]): ...

Child[bytes]  # error: Child is not generic
```

The existing fallback treated any class with `Generic` anywhere in its
MRO as generic. That fallback predates our support for legacy generic
contexts and is now overly broad: `Generic` remains in the runtime MRO
even when the class has no free type variables. Actual generic class
literals are already accepted through their generic context, so we can
remove the MRO fallback and report `not-subscriptable` for other
classes.

This brings ty into conformance with the
`generics_defaults_specialization.py` case from
[python/typing#2325](https://github.com/python/typing/pull/2325) and
adds focused mdtest coverage.
2026-07-31 15:44:49 -04:00
Brent Westbrook 31cb63ae5e [flake8-pyi] Avoid false positives on singledispatch functions (PYI041) (#27335)
Summary
--

Fixes #27333. The fixed annotation isn't actually equivalent in this
case.

```pycon
>>> import functools
...
... @functools.singledispatch
... def int_or_float(value: object) -> str:
...     return False
...
... @int_or_float.register
... def _(value: int | float):
...     return True
...
... assert int_or_float(3)
...
>>> import functools
...
... @functools.singledispatch
... def int_or_float(value: object) -> str:
...     return False
...
... @int_or_float.register
... def _(value: float):
...     return True
...
... assert int_or_float(3)
...
Traceback (most recent call last):
  File "<python-input-1>", line 11, in <module>
    assert int_or_float(3)
           ~~~~~~~~~~~~^^^
AssertionError
```

We had an existing helper for testing for `singledispatch` functions,
which we use to skip the first
parameter of such functions since this is the dispatching parameter.

Test Plan
--

New mdtests
2026-07-31 12:19:43 -04:00
Brent Westbrook 7111e134a4 Register formatting capabilities dynamically to exclude TOML files (#27332)
Summary
--

Addresses
https://github.com/astral-sh/ruff-vscode/pull/1127#discussion_r3684426847,
which would
otherwise expose Ruff as a TOML formatter in VS Code. Instead of
registering formatting and range
formatting capabilities statically, we now register them dynamically
only for Python, Markdown, and
notebook files, excluding TOML files.

We still fall back on static registration if dynamic registration is
unavailable.

Test Plan
--

New e2e tests
2026-07-31 10:10:09 -04:00
JSandMicha Reiser 2b85a51d84 [ty] Index with-statement targets as symbols (#27256)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-07-31 15:45:25 +02:00
David Peter 7a7b2abf60 [ty] Use legacy ParamSpec syntax in legacy wrapper test (#27348)
## Summary

Use legacy ParamSpec syntax in `legacy/paramspec.md`. Previously, this
was just a verbatim copy of [this PEP 695
test](https://github.com/astral-sh/ruff/blob/4a437b093417a22abecc168cc186ef58ca48c0fc/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md?plain=1#L436-L463).
2026-07-31 03:48:28 -07:00
Charlie Marsh 88afdefde0 [ty] Diagnose dataclass fields after inherited defaults (#27327)
## Summary

We now enforce dataclass constructor field ordering across inherited
fields, including `@dataclass_transform` classes.

```python
from dataclasses import dataclass

@dataclass
class Base:
    x: int = 1

@dataclass
class Child(Base):
    y: int  # error: [dataclass-field-order]
```

Fields inherited from `@dataclass_transform(kw_only_default=True)`
remain keyword-only, and overriding an inherited field with `ClassVar`
removes it from the generated constructor without incorrectly excluding
`InitVar` overrides.

Closes https://github.com/astral-sh/ty/issues/4125.
2026-07-31 01:52:08 -07:00
David Peter 8ebf0eb8fa [ty] Restore frozen-collection APIs (#27367)
## Summary

Revert #27347 and restore the consuming `IntoIterator` implementation
for `FrozenMap`.

See comment by Micha
[here](https://github.com/astral-sh/ruff/pull/27347#discussion_r3689046712).
2026-07-31 01:48:54 -07:00
David Peter 9dfeff4c0f [ty] Remove single-use current-assignment conversions (#27361)
## Summary

Remove single-use current-assignment conversions.
2026-07-31 01:22:32 -07:00
David Peter 0e24940bd1 [ty] Reuse tuple length accessors for size hints (#27366)
## Summary

Reuse tuple length accessors for size hints.
2026-07-31 01:21:17 -07:00
David Peter 81f97ec410 [ty] Remove empty corpus known-failure machinery (#27344)
## Summary

Remove empty corpus known-failure machinery.
2026-07-31 01:21:01 -07:00
David Peter cf3ddc324c [ty] Merge duplicated VecSet implementation blocks (#27365)
## Summary

Merge duplicated VecSet implementation blocks.
2026-07-31 01:20:09 -07:00