10214 Commits

Author SHA1 Message Date
Erik Johnson ea6be89b0a Increase max value of line-length setting (#24962)
Co-authored-by: Erik Johnson <source@ekriirke.net>
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-05-05 14:08:51 +00:00
Lérè a8d3850605 [ty] Selectively promote a union of homogeneous fixed-length tuples to a single variadic tuple. (#24705)
<!--
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

This implements structural promotion of tuple size in the inferred type
of a collection literal.

The promotion only applies to a very specific circumstance: when tuple
literals in an inferred collection element type produce a union of
homogeneous fixed-length tuples of differing lengths, and only literal
tuple sources have contributed to that type, then we widen that union to
a single variadic tuple (e.g., `tuple[str] | tuple[str, str]` is widened
to `tuple[str, ...]`).

The result is that this scenario described in
https://github.com/astral-sh/ty/issues/2620 succeeds:

```python
languages = {
    "python": (".py", ".pyi"),
    "javascript": (".js", ".jsx", ".ts", ".tsx"),
}

# This no longer errors after this change, because the type of languages is `dict[str, tuple[str, ...]` rather than `dict[str, tuple[str, str]] | tuple[str, str, str, str]]`
languages["ruby"] = (".rb",) 
```

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

### Approach

- I created a new submodule that encapsulates the tuple size promotion
policy. It exposes a `TupleSizePromotionConstraints` struct that we use
during inference to record the scenarios in which we should **not**
attempt to promote a tuple. If no such disqualifying scenarios are
encountered, then tuple size promotion is attempted. The set of
disqualifying scenarios is documented in new mdtests.
- I think the policy for when to promote unions that involve empty
tuples deserves particular scrutiny. Since empty tuples do not have an
element type, they present a special case. The rule I've chosen is that
empty tuples do not contribute to evidence of different tuple lengths.
That means that a union containing an empty tuple must also contain
other tuples of differing lengths to trigger promotion (i.e., `[(),
(1,)]` remains `list[tuple[()] | tuple[int]]`, but `[(), (1,), (1,2)]`
is promoted to `list[tuple[int, ...]]`. This is conservative and, I
hope, useful for modeling situations in which the size of a tuple is
specifically meant to be 0 or N.

## Test Plan

Please see new and updated mdtests.

<!-- How was it tested? -->
2026-05-04 11:31:21 -07:00
Charlie Marsh a88b2f619c [ty] Cache results in desperate module resolution (#24977)
## Summary

Per Codex, in pyx, this adds 1.3MB to the cache (0.4%) in return for a
5-10% faster walltime.
2026-05-04 06:54:41 -07:00
Shunsuke Shibayama b409cbeea6 [ty] implement proper handling of recursive types in CycleDetector (#24773)
## Summary

This fixes the known issues with handling recursive aliases, as
described
[here](https://github.com/astral-sh/ty/issues/3195#issuecomment-4184926298)
and elsewhere.

```py
from typing import reveal_type

type A = list[A]

def foo(x: A):
    reveal_type(x[0])  # main: list[Any] -> this PR: list[A]
```

This allows us to safely remove the `MAX_RECURSION_DEPTH` limit that was
associated with `CycleDetector`.

The point is that in the previous implementation, we simply compared
types using hash values ​​to guard against recursion, but this was
insufficient. By manually adding equality checks, the depth limit is no
longer necessary.

Stacked on https://github.com/astral-sh/ruff/pull/24803

## Test Plan

mdtest updated

---------

Co-authored-by: Charlie Marsh <charlie.r.marsh@gmail.com>
2026-05-02 21:26:11 -04:00
Charlie Marsh e002c44494 [ty] Skip parameter accumulation for object variadics (#24976) 2026-05-02 20:41:55 -04:00
Charlie Marsh 5f040fab4b [ty] Expand support for narrowing within walruses (#24968)
## Summary

Support narrowing for a few more already-supported sites, but in the
context of a walrus, as in:

```python
def f(t: tuple[int, int] | tuple[None, None]):
    if (first := t[0]) is not None:
        reveal_type(first)  # int
        reveal_type(t)      # tuple[int, int]
    else:
        reveal_type(first)  # None
        reveal_type(t)      # tuple[None, None]
```
2026-05-01 23:09:36 -04:00
Charlie Marsh b1fe4e405d [ty] Fix missing visitor guard in Callable branch (#24964)
## Summary

This came up in a subsequent PR, but I think it's just missing here.
2026-05-01 17:06:23 -04:00
Charlie Marsh e990dfd069 [ty] Allow reference finding in stringified annotations (#24956)
## Summary

Stringified annotations were being guarded by the `includeDeclaration`
flag, so `MyClass` inside `"MyClass"` wasn't being treated as a
reference, which seems unintentional.

Closes https://github.com/astral-sh/ty/issues/3386.
2026-05-01 12:35:11 -04:00
Charlie Marsh 81c81f6892 [ty] Unpack Union of TypedDict in various sites (#24958)
## Summary

We already have a helper for this; we just weren't using it everywhere.
2026-05-01 12:18:58 -04:00
Shunsuke Shibayama c6057e034b [ty] Fix unbounded type growth in nested-typevar substitutions (#24803) 2026-05-01 15:01:14 +09:00
github-actions[bot] 9e2dd4ab4d [ty] Sync vendored typeshed stubs (#24952)
Close and reopen this PR to trigger CI

---------

Co-authored-by: typeshedbot <>
2026-04-30 22:16:10 -04:00
Charlie Marsh b3fb864847 [ty] Show a diagnostic for unsupported inferred Python version (#24581)
## Summary

This PR adds the infrastructure to surface diagnostics during project
and program settings resolution. The motivating use-case is: when we see
an unsupported Python version in an editor, we want to surface a
diagnostic rather than limiting to a `tracing` warning, as in:

```
warning[unsupported-python-version]: Ignoring unsupported inferred Python version `3.16`; ty will use Python 3.14 instead.
 --> venv/pyvenv.cfg:2:16
  |
2 | version_info = 3.16.0
  |                ^^^^^^
3 | home = base/bin
  |
info: Expected one of `3.7`, `3.8`, `3.9`, `3.10`, `3.11`, `3.12`, `3.13`, `3.14`, `3.15`.
info: Set `python-version` explicitly to override the inferred version.
info: The version was inferred from your virtual environment metadata.
```
2026-04-30 15:08:38 -04:00
Charlie Marsh 95670c1f56 [ty] Model functools.partial call results (#24582)
## Summary

This PR adds initial support for `functools.partial`, including:

- Constructor-time checking of bound arguments (e.g., `partial(f, "x")`
should report an immediate error if `"x` is not a valid type for the
parameter)
- Reduced signatures for partials (e.g., `def f(a: int, b: str, *, c:
bool) -> bytes` with `partial(f, 1)` becomes `partial[(b: str, *, c:
bool) -> bytes]`).
- Support for overloads, assignability checks, and more.

There are a few things that are _not_ covered and were instead cordoned
off into separate commits, namely:

- Preserving unprovided generic type variables in the returned partial
signature (fixed in: https://github.com/astral-sh/ruff/pull/24583). As
of this commit, we get:

```python
from functools import partial
from typing import TypeVar

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

def combine(a: T, b: U) -> tuple[T, U]:
    return (a, b)

# partial[(b: Unknown) -> tuple[Literal[1], Unknown]]
p = partial(combine, 1)
```

- Keyword overrides in generics (e.g., `partial(combine, b=1)` can later
be called as `p("x", b="y")`, since keyword arguments can be overridden
at call time -- TIL!).
- Constructor modeling (`__new__`, etc.)

But this gets us much of the way there. After this PR, I believe our
handling of `functools.partial` is generally ahead of Mypy and Pyright
with the significant exception of generic modeling, where ty is behind.

(I choose to include tests for the above in
`crates/ty_python_semantic/resources/mdtest/call/functools_partial.md`,
with TODOs, which get resolved in subsequent PRs.)

See: https://github.com/astral-sh/ty/issues/1536.
2026-04-30 11:28:06 -04:00
Charlie Marsh 7eb38d121c [ty] Avoid expression_type calls for syntax error targets in unpacking assignment (#24663)
## Summary

We parse `something, not = (1, 2)` as (on the LHS) a name target
(`something`) and a unary target (`not`) followed by an empty name. As a
result, we never visit the `not` or its name, which means we never infer
or record a type for that malformed subtree in `UnpackResult`.

Later, an `expression_type(...)` lookup for any subexpression can miss
and panic.

Closes https://github.com/astral-sh/ty/issues/3283.
2026-04-30 09:29:33 -04:00
Charlie Marsh 81057044f4 [ty] Guard recursive protocol signature comparisons (#24665)
## Summary

This PR adds a recursion guard for signature comparisons (keyed by
(source definition, target definition, relation)), used to prevent a
stack overflow in structural protocol matching. Previously, recursive
protocols would just recurse with a new specialization; now, we assume
success when we see the same pair in a single check, and continue from
there.

Closes https://github.com/astral-sh/ty/issues/3208.
2026-04-30 12:45:01 +00:00
Denys Zhak fba03a6b47 Fix F811 false positive for class methods (#24933) 2026-04-30 13:33:56 +02:00
Eyüp Can Akman f32733c063 [flake8-pyi] Fix PYI016 false positive for f-string debug specifier (#24098)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-04-30 11:14:25 +02:00
Charlie Marsh 97cf85d454 [ty] Fix TypeIs assignability with gradual types (#24928)
## Summary

Prior to this change, for `TypeIs`, we only stored the
`T.top_materialization()` of the type. So for assignability, we checked
against the materialized narrowing type, rather than the user-declared
type. The two are conflated.

I think this causes problems for cases like:

```python
static_assert(is_assignable_to(TypeIs[Sequence[int]], TypeIs[Sequence[Any]]))
static_assert(not is_assignable_to(TypeIs[Sequence[int]], TypeIs[Sequence[object]]))
```

On `main`, the first assertion fails.
2026-04-29 17:08:21 -04:00
Charlie Marsh e6528502ba [ty] Support infer_variance for legacy TypeVar (#24930)
## Summary

See:
https://github.com/astral-sh/ruff/pull/24927#discussion_r3162016951.
2026-04-29 12:49:34 -04:00
Matthew Mckee 1a794b966a Add hover support for pep 695 type alias (#24926) 2026-04-29 16:23:49 +00:00
Lérè e97bab008e [ty] Prevent quoted annotation tokens from leaking across notebook cells. (#24919)
<!--
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

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

This prevents semantic tokens from a quoted type annotation from leaking
across notebook cells. Previously, such a leak would cause wonky syntax
highlighting as described in
https://github.com/astral-sh/ty/issues/3307.

The root cause of the issue was accidental omission of a cell's source
range from the filter used to determine the bounds of the semantic token
request.

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

## Test Plan

Please see added regression test.
<!-- How was it tested? -->

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
2026-04-29 16:17:06 +00:00
Charlie Marsh 3cec7f2315 [ty] Support variance keywords in ParamSpec (#24927)
## Summary

This PR adds `covariant`, `contravariant`, and `infer_variance` support
to `ParamSpec`.

See: https://github.com/python/typing/pull/2215.

See:
https://github.com/astral-sh/ruff/pull/24479#pullrequestreview-4192292282.
2026-04-29 11:30:39 -04:00
Anders Brams bfe5b51890 [ty] Offer string literal completion suggestions based on expected type (#24555)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-04-29 17:16:58 +02:00
Charlie Marsh ea4b40641a Restrict PYI034 for in-place operations to enclosing class (#24511)
## Summary

In `.py` and `.pyi` files, we now only flag cases in which the return
type is the enclosing class, like:

```python
class A:
    def __iadd__(self) -> A:
        return self
```

As opposed to:

```python
class A:
    def __iadd__(self) -> int:
        return self
```

Closes https://github.com/astral-sh/ruff/issues/24462.
2026-04-29 10:16:00 -04:00
Charlie Marsh 1b931ba658 [ty] Fix ParamSpec defaults and alias variance (#24479)
## Summary

This PR fixes several ParamSpec variance and gradual-specialization edge
cases that fell out of https://github.com/astral-sh/ruff/pull/24319.

We also now treat `typing_extensions.ParamSpec` defaults like
`typing.ParamSpec` defaults, which I think was an oversight.
2026-04-29 09:04:45 -04:00
David Peter cf14481f44 Semantic syntax errors: better error message for global vs parameter (#24902)
## Summary

This is an attempt to improve the error message for something like
```py
a = 1

def f(a):
    global a
```

I realize that the previous wording ("name 'a' is parameter and global")
is used by CPython itself, but unless we are trying to be consistent
with CPython, I think we can improve upon this? The previous version
seemed a bit cryptic to me when I first saw it.

## Test Plan

Updated snapshot tests
2026-04-29 08:46:57 +02:00
David Peter 524158dbd0 [ty] Add missing error context node for protocol to protocol assignability (#24905)
## Summary

Thank you for spotting this, @carljm.

## Test Plan

New Markdown tests
2026-04-29 08:46:09 +02:00
Charlie Marsh 1ccaf7c9e5 [ty] Store call argument data together (#24851)
## Summary

These values are typically accessed together (e.g.,
`ArgumentMatcher::new`), and the construction now halves the number of
vector allocations.
2026-04-28 14:01:37 -04:00
Charlie Marsh 0ea258f0da [ty] Lazily build TypeVar accumulations (#24782)
## Summary

Given a generic specialization, we were rebuilding the constraints after
every argument, rather than all-at-once.

E.g., for:

```python
def combine[T](a: T, b: T, c: T, d: T) -> T:
    return a

combine(("name", 1), ("id", 2), ("flag", True), ("size", 4))
```

Each argument constrains the same type variable `T`:

```python
T = tuple[Literal["name"], Literal[1]]
T = tuple[Literal["id"], Literal[2]]
T = tuple[Literal["flag"], Literal[True]]
T = tuple[Literal["size"], Literal[4]]
```

On main, we then compute (roughly):
```
T = A
T = union(A, B)
T = union(union(A, B), C)
T = union(union(A, B, C), D)
```

Now, we create a builder and construct at the end. This has a
significant impact on functions with many arguments, but also reduces
memory on real-world projects, which is great.
2026-04-28 12:22:33 -04:00
Charlie Marsh 975e029640 [ty] Add a benchmark for TypeVar accumulation (#24781)
## Summary

Adds a microbenchmark to assess
https://github.com/astral-sh/ruff/pull/24782.
2026-04-28 11:56:55 -04:00
Micha Reiser dfba807cc8 Goto definition, declaration, find references for typed dict and named tuple initializers (#24897)
Co-authored-by: Viicos <65306057+Viicos@users.noreply.github.com>
2026-04-28 15:46:15 +00:00
Micha Reiser 59c6c1f1a8 [ty] Add rename to playground (#24899) 2026-04-28 17:21:27 +02:00
Douglas Creager eccc6ce4fb [ty] Cache constraint set solutions (#24057)
This adds some salsa-caching to the new constraint set solver. There are
many call paths that build up a constraint set for an assignability
check, and then solve that constraint set to get a specialization, and
we often have to perform this multiple times on the same two types.

It is not just constraint set construction that is worth caching; we
also want to cache as much of the solution extraction as we can. So this
PR refactors the solving code slightly so that `PathBounds` is the
result of the now-salsa-cached method. This is a vec with an element for
each satisfiable path in the constraint set BDD, recording the combined
lower and upper bound for each typevar mentioned on that path. This
caches us much of the work as we can, while still allowing different
callers to provide different `choose` callbacks if they need to override
how to choose a specific type within that lower/upper bound, or if they
need to record additional information about the solution paths.
2026-04-28 10:33:38 -04:00
Charlie Marsh 9742dcb319 [ty] Infer dict(**TypedDict) in TypedDict context (#24709)
## Summary

Given, e.g., `def f() -> TD: return dict(**src)`, we now infer
`dict(**src)` as matching `TD` if `src` is `TD`, for example. So the
following are accepted, whereas on main they all produce diagnostics:

```python
from typing import TypedDict

class TD(TypedDict):
    x: int
    y: str

src: TD = {
    "x": 1,
    "y": "foo",
}

x: TD = dict(**src)

def f() -> TD: return dict(**src)
```
2026-04-28 09:55:55 -04:00
David Peter 150a74766f [ty] Convert not-iterable tests to inline snapshots (#24901)
## Summary

I'm about to make changes to `not-iterable` diagnostics, so let's
migrate them to inline snapshots first.
2026-04-28 15:11:10 +02:00
Micha Reiser 6cf53e057d [ty] Reduce the diagnostic context window in ty_ide tests to 0 (#24900) 2026-04-28 14:57:59 +02:00
Denys Zhak 5a74b374a0 [ty] handle Annotated metadata in semantic tokens (#24890)
Fixes https://github.com/astral-sh/ty/issues/3362

## Summary

Fix semantic token classification for `typing.Annotated` metadata.

## Test Plan

Added test.
2026-04-28 11:44:28 +02:00
Carl Meyer 87443c22d4 [ty] prefer declared type if mutually assignable (#24802) 2026-04-27 19:34:42 -07:00
Charlie Marsh 19f335a893 [ty] Emit diagnostic for invalid uses of Unpack[...] (#24868)
## Summary

This PR adds stricter validation for uses of `Unpack[...]`, namely to
ensure that we still reject `Unpack` if it's in a `**kwargs` annotation
but _not_ the top-level construct (e.g., `**kwargs: list[Unpack[TD]]`,
`**kwargs: Unpack[TD] | int`).
2026-04-27 20:33:45 -04:00
Charlie Marsh 359eb82272 [ty] Fix receiver coloring for aliased decorators (#24884)
## Summary

We already expose `is_classmethod`, etc., on function type, and those
methods already support aliasing, so the LSP just uses those directly
now.

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

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
2026-04-27 18:03:14 -04:00
Carl Meyer 398262cb8f [ty] handle finally blocks where all try/except are terminal (#24882) 2026-04-27 12:01:57 -07:00
Charlie Marsh 3ef310100a [ty] Detect invalid attribute overrides (#24767)
## Summary

We now detect Liskov violations when a parent-child pair have attributes
with a differing `ClassVar` status.

In general, we interpret an attribute without a `ClassVar` annotation as
an instance attribute, with the exception of cases like the following,
where we "allow" the child to "inherit" the annotation to adhere to the
conformance suite:

```python
class ProtoB(Protocol):
    z: ClassVar[int]

class ProtoBImpl(ProtoB):
    z = 0
```

There are a few other tricky cases to consider.

### Methods

Like Mypy and Pyright, we don't flag the following:

```py
from typing import Any, Callable, ClassVar

class Base:
    f: ClassVar[Callable[..., Any]]

class Sub(Base):
    def f(self) -> int:
        return 1
```

### Descriptors

Like Mypy and Pyright, we don't flag the following:

```python
class Descriptor:
    def __get__(self, obj: object, owner: type[object]) -> int:
        return 1

class Base:
    attr = Descriptor()

class Sub(Base):
    attr: int
```

### Properties

Like Mypy and Pyright, we _do_ flag the following, since it changes the
class-vs.-instance contract:

```python
from typing import ClassVar

class Base:
    attr: ClassVar[int]

class Sub(Base):
    @property
    def attr(self) -> int:  # error: [invalid-attribute-override]
        return 1
```

#### Final

We don't flag the following, because it already has a dedicated
diagnostic:

```python
from typing import Final

class Base:
    attr: Final[int] = 1

class Sub(Base):
    attr = 2  # error: [override-of-final-variable]
```

Closes https://github.com/astral-sh/ty/issues/3093.
2026-04-27 14:24:23 -04:00
Alex Waygood 6bfbd8abfd Speed up CI workflows by setting line-tables-only for debug info (#24833) 2026-04-27 10:59:09 -04:00
Matthew Mckee d9957f939b [ty] Don't allow inlay hint edits when introducing a non global scope symbol (#24797) 2026-04-27 11:39:02 +02:00
Micha Reiser 043a8583eb Always include panic payload in panic diagnostic message (#24873) 2026-04-27 11:25:00 +02:00
Tamir Duberstein f508a69b70 [ty] Model bool-op branch snapshots (#24458)
Co-authored-by: Carl Meyer <carl@astral.sh>
2026-04-26 19:14:08 -07:00
Charlie Marsh 10c0bb3110 [ty] Support Unpack[TypedDict] in **kwargs signatures (#24653)
## Summary

We now support `Unpack[TypedDict]` as an annotation on `**kwargs`, as in
the following example:

```python
from typing_extensions import TypedDict, Unpack

class MovieKwargs(TypedDict):
    title: str
    year: int

def show_movie(**kwargs: Unpack[MovieKwargs]) -> None:
    ...

show_movie(title="Alien", year=1979)  # OK
show_movie(title="Alien")             # missing required key
show_movie(name="Alien", year=1979)   # unknown keyword
```
2026-04-26 22:11:26 -04:00
Charlie Marsh c7c24b17bf [ty] Reserve union element storage (#24849)
## Summary

Pre-allocate the vector since we know the size upfront.
2026-04-26 17:58:47 -04:00
Ibraheem Ahmed 3de40f7d46 [ty] Pass unmapped type variables to SpecializationBuilder::build_with (#24809)
This is a small refactor, and means that we no longer have to manually
compute the constraint set solutions just to change the default
specialization of a given mapping.
2026-04-26 16:11:56 -04:00
Charlie Marsh 1d287faf01 [ty] Avoid bookkeeping for unannotated functions (#24842)
## Summary

If a function doesn't contain any annotations or default arguments, we
don't need to do deferred inference for the signature; and if the
function isn't decorated, we don't need to store a separate
`undecorated_type`. This avoids a deferred-definition entry, an empty
deferred inference query, and (oftena) an `DefinitionInferenceExtra`
allocation.
2026-04-25 21:47:16 -04:00