Commit Graph
17123 Commits
Author SHA1 Message Date
Micha Reiser a99b96b9ac Expose hidden lexer token metadata accessors (#28443) 2026-09-09 09:02:54 +02:00
renovate[bot] 55df129251 Update NPM Development dependencies (#28438) 2026-09-09 07:59:31 +02:00
renovate[bot] 7158e733bb Update prek dependencies (#28432) 2026-09-09 07:58:58 +02:00
renovate[bot] da6a16bbdb Update actions/attest-build-provenance action to v4.2.2 (#28437) 2026-09-09 07:53:03 +02:00
renovate[bot] 17c53bb90e Update CodSpeedHQ/action action to v5.0.3 (#28430) 2026-09-09 07:52:41 +02:00
renovate[bot] 6eaa5bf8f0 Update dependency astral-sh/uv to v0.12.11 (#28431) 2026-09-09 07:52:19 +02:00
renovate[bot] c2b1655f8d Update Swatinem/rust-cache action to v2.9.2 (#28436) 2026-09-09 07:51:51 +02:00
renovate[bot] 7365a3769c Update Rust crate thiserror to v2.0.20 (#28435) 2026-09-09 07:51:11 +02:00
renovate[bot] 89ff38ada9 Update Rust crate clap to v4.6.6 (#28433) 2026-09-09 07:50:19 +02:00
renovate[bot] df162026af Update Rust crate rkyv to v0.8.18 (#28434) 2026-09-09 07:49:48 +02:00
Ibraheem Ahmed d183429e58 [ty] Infer tuple variance more precisely (#28426)
When inferring the variance of a tuple type, we currently go through
the`Tuple::tuple_class_type` helper, which unions all elements of the
tuple. This can lead to type variables getting simplified out of the
target type, e.g., in the case of `tuple[T, object]`, leading to
incorrect variance results. This PR adds a separate path that infers
variance from the elements of the tuple directly.
2026-09-08 21:09:02 -04:00
Charlie Marsh ffeadbdcb4 [ty] Fast-path concrete literal intersections (#28348)
## Summary

Built-in literal intersections intern a `TypePair` and enter a tracked
Salsa query even when the two literal values fully determine the result.
We now simplify concrete `int`, `bool`, `str`, and `bytes` literal pairs
directly, preserving promotability and recursive-definition semantics.
These relations have no inference dependencies, so they cannot
participate in inference cycles.
2026-09-08 19:35:26 -04:00
Freya Bruhin d136bd8d00 Fix line-length path in --config example (#28392)
line-length is a global option, not under `lint.`

This was introduced in #25013, after which #25389 attempted to fix it.
However, that change was made in autogenerated docs only (instead of
fixing it at the source), and thus the fix was undone again in #25396.

This instead fixes the issue in the source code, followed by a `cargo
dev generate-all`.
2026-09-08 18:29:59 -04:00
NighttandBrent Westbrook f3280423f6 [flake8-tidy-imports] Skip fixes for multi-member imports (TID254) (#26584)
This fixes #26450.

TID254 now reports policy violations without offering a fix when an
import statement contains multiple names. Single-name imports remain
fixable. This prevents conflicting lazy-import requirements from
repeatedly adding and removing `lazy`, and uses the same single-member
check as TID255.

The tests cover both `import` and `from ... import ...`, including
conflicting policies and fixes in both directions.

btw: cuz i not the natvie English speaker, i will use ai to imporve my
txt, which may let it look like a AI generate....

---------

Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
2026-09-08 18:29:44 -04:00
Douglas Creager d38008443d [ty] Use correct top type for default declared upper bound (#28325)
There is quite a lot of code that assumes that an unbounded typevar has
an implicit upper bound of `object`. But this is only true for regular
`TypeVar`s. `ParamSpec`s, for instance, have the top `paramspec_value`
callable as their implicit upper bound.

This patch adds a new `TypeVarDomain` type (also needed for #28249) that
describes what kind of values the typevar takes on in a specialization,
and a method that returns the correct top type for each domain.

Most callers should use `require_bound_or_constraints` to get the upper
bound or constraints of a typevar, without having to worry about whether
the upper bound is implicit or declared. It now returns the correct
implicit upper bound for the particular kind of typevar. There were many
places that were calling `bound_or_constraints`, trying to add a
fast-path for `object` where possible. But since this is only valid for
plain `TypeVar`s, I've updated most of those callers to just call
`require_bound_or_constraints` and not over-think the optimization.
(Most of the time there's a recursive call that will return quickly for
`object` anyway.)
2026-09-08 14:05:43 -04:00
Alex Waygood 4cdd004a71 [ty] Preserve redundant-condition diagnostics with unreachable operands (#28374) 2026-09-08 17:21:40 +00:00
Douglas Creager a063c73bf7 [ty] Ignore removed return value when materializing paramspec values (#28323)
Similar to #28315, we should ignore the removed return type of a
paramspec value when calculating its top and bottom materializations.
(We actually go one step further and skip it for all `TypeMapping`s)

(This tells me we might want to consider a different representation for
paramspec values, but that's not "drop everything and do it now"
urgent.)
2026-09-08 13:09:07 -04:00
Charlie Marsh 7d1432f809 [ty] Avoid excess capacity in multi-binding tables (#28412)
## Summary

`MultiBindingsByUse` currently builds and sorts a temporary `Vec`, then
allocates again when collecting into a `ThinVec`. For tables with one to
three entries, that final collection also retains four slots.

We now build the `ThinVec` directly with capacity reserved to the map's
length and sort it in place, removing the intermediate allocation and
excess capacity.
2026-09-08 10:52:26 -04:00
Charlie Marsh b7122efb9e Delegate Name string comparisons to CharStr (#28401)
Delegate `Name` equality with borrowed strings to `CharStr`, including
Salsa lookups, so these callers use the compact string's comparison
implementation.

This connects Ruff's name comparisons to the short-string fast paths in
https://github.com/astral-sh/char_str/pull/40. Those fast paths require
a later `char_str` release; this change is compatible with the current
dependency.
2026-09-08 09:46:28 -04:00
Alex WaygoodandMicha Reiser 12132db288 [ty] Isolate uv integration tests from inherited environment settings (#28342)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-09-08 13:09:55 +02:00
Alex Waygood 321eebc2d6 [ty] Fix disjointness of type guards and boolean literals (#28363) 2026-09-08 11:00:10 +01:00
Micha Reiser 12e65d6d7e [ty] Prevent LSP hangs during inlay hint bursts (#28390) 2026-09-08 10:16:40 +02:00
Micha Reiser 36ac20923a [ty] Rebalance instrumented microbenchmark shards (#28406) 2026-09-08 09:10:03 +02:00
Charlie Marsh e7adf82ff0 Reuse parser name lookups when interning (#28399)
Use hashbrown's borrowed-entry insertion for long parser names so a
missing name reuses its lookup when it is inserted. Construct the
heap-backed `Name` only on a miss, while preserving the inline bypass
and shared allocations for repeated long names.
2026-09-07 19:00:20 -04:00
Charlie Marsh f6cdb46fd7 [ty] Share names in synthesized constructor parameters (#28398)
When synthesizing constructor parameters for dataclasses and named
tuples, we rebuild each unaliased field name from `&str`. Names longer
than 16 bytes therefore allocate again even though the field already
owns shared storage.

Clone the existing `Name` when no alias is present so the generated
parameter shares its allocation with the field. Aliased parameters
retain their existing names.
2026-09-07 17:37:39 -04:00
Charlie Marsh 9410344db6 [ty] Normalize recursive collection-use constraints (#28332)
## Summary

We now finish inferring nested unannotated dictionaries such as this
example, which previously exceeded a 30-second timeout:

```python
def f(flag: bool):
    x = {}
    y = {}
    return {"a": x, "b": {"c": y} if flag else {"d": {"e": y}}}
```

The saved collection-use constraints feed back into the empty
dictionaries' initializers. Without normalization, each cycle iteration
adds another layer of dictionary types. We now apply the same
recursive-type normalization used for expression and binding types to
these constraints, allowing inference to converge.

Concretely, for this example, we:

- Infer `y = {}`
- Inspect constraints from the `return` statement
- Infer the `return` statement, which reads `y`
- Re-enter inference for `y = {}`

Within that `return`, we see `{"c": y} if flag else {"d": {"e": y}}`. If
`Y` is our current estimate of `y`'s type:

- The false branch has type `dict[str, dict[str, Y]]`
- We use that branch as contextual guidance for the true branch
- Therefore, the value `y` in `{"c": y}` gets an expected type of
`dict[str, Y]`
- We save that expectation as a constraint on `y`’s initializer

Inferring `y` from that saved constraint feeds the additional dictionary
layer back into the next iteration. If we start with `Y₀ =
dict[Divergent, Divergent]`, the saved constraints grow like:

- `dict[str, Y₀]`
- `dict[str, dict[str, Y₀]]`
- `dict[str, dict[str, dict[str, Y₀]]]`

And they're stored in `extra.collection_use_constraints`. We already
normalize expression and binding types during cycle recovery, but
weren’t normalizing this field, even though it also feeds back into
inference.

The fix applies that same normalization to the saved constraints,
reducing these expanding forms to `dict[str, Divergent]`. The next
iteration produces the same normalized constraint, allowing inference to
converge.

Closes https://github.com/astral-sh/ty/issues/4466.
2026-09-07 17:03:47 -04:00
David Peter 70463c09d2 [ty] Discover configs in models derived from legacy generic classes (#28397)
## Summary

Skip `Generic[T]` bases when discovering Pydantic model configuration so
legacy generic classes preserve inherited settings.

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

## Test plan

Regression tests
2026-09-07 20:15:16 +02:00
renovate[bot]andMicha Reiser c6139b08e7 Update Rust crate get-size2 to 0.11.0 and char_str to 0.0.4 (#27685)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-09-07 18:28:51 +02:00
Alex WaygoodandDavid Peter eb9396423e [ty] Ensure f is identity(f) evaluates to Literal[True] (#28360)
Co-authored-by: David Peter <sharkdp@users.noreply.github.com>
2026-09-07 16:19:15 +00:00
Qatadaha Bin MatloobandMicha Reiser b3fb43af25 Alternate nested quotes inside format spec interpolations (#28259)
Co-authored-by: Micha Reiser <micha@reiser.io>
2026-09-07 17:53:16 +02:00
David Peter b6fec44b02 [ty] Solve formal unions using constraint-sets (#28312)
## Summary

When comparing an "actual" typevar against a "formal" union, we now use
the new constraint-set based solver instead of the legacy solver. The
previous implementation did not respect (co)variance correctly.

```py
class Contravariant[T]:
    def __init__(self: Contravariant[int | str]) -> None: ...

    # members to make it contravariant in `T`

reveal_type(Contravariant())  # revealed: previously an error, now Contravariant[int | str]
```

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

## Ecosystem

The two new diagnostics are correct; mypy and pyright agree with that
behavior.

<details>

Reproducer:

```py
from typing import Any, Generic
from typing_extensions import TypeVar

T = TypeVar("T", default=str, covariant=True)

class KDE(Generic[T]):
    def __init__(self: "KDE[str | Any]") -> None: ...

reveal_type(KDE())
```

- Base: KDE[str]
- PR: KDE[str | Any]
- Pyright and mypy agree with the PR.

</details>

## Test Plan

New Markdown tests
2026-09-07 17:47:00 +02:00
David Peter 05da8ec80a [ty] Preserve protocol intersections when narrowing gradual types (#28391)
## Summary

Fix non-strict `TypeIs` and `isinstance` narrowing of gradual
implementations with fully static protocols:

```py
@runtime_checkable
class IntReader(Protocol):
    def read(self) -> int: ...

class GradualReader:
    def read(self) -> Any: ...

def _(obj: GradualReader):
    if isinstance(obj, IntReader):
        reveal_type(obj)  # revealed: GradualReader & IntReader, previously GradualReader
        reveal_type(obj.read())  # revealed: Any & int, previously `Any`
```

We now preserve the original type (`GradualReader`) only when it is a
subtype of the protocol's top materialization. Otherwise, we intersect
with `IntReader`.

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

## Test Plan

New regression test. I chose not to include the exact example from
https://github.com/astral-sh/ty/issues/4479 (but that example *does*
pass now, and `x.read("value")` is inferred as `int`), since I find that
harder to understand and because of
https://github.com/astral-sh/ty/issues/4480
2026-09-07 17:08:00 +02:00
Charlie Marsh 7ab68ebb82 [ty] Lazily compute argument-context specializations (#28378)
## Summary

Collecting call-argument contexts computes a generic specialization for
every candidate overload before inspecting its arguments, even when no
parameter uses that specialization. We now compute it on the first
request and share the result across arguments for that overload.

The cache lives within one context-collection pass. Empty calls,
unmatched arguments, and parameters whose context comes directly from a
type-variable bound can avoid the computation entirely. Argument
traversal order and the specialization calculation are unchanged.
2026-09-07 10:58:29 -04:00
David Peter efb30c1f4c [ty] Align TypeIs and isinstance narrowing (#28193)
## Summary

This PR aligns `TypeIs` and `isinstance` narrowing for unspecialized
generics and gradual protocols in both strict and non-strict narrowing
modes:
- if `analysis.strict-generic-narrowing = true`, we now intersect with
the top materialization of the generic/gradual type, i.e. for a type
guard returning `TypeIs[list]`, we now narrow with the (typically
unspellable) `Top[list[Unknown]]`. Previously, we did not do this
consistently for `isinstance` narrowing either when narrowing to a
gradual protocol.
- if `analysis.strict-generic-narrowing = false` (default), we now use
the same "union filtering" approach that we also use for `isinstance`
narrowing (note that the [`TypeIs`
PEP](https://peps.python.org/pep-0742/) specifies the narrowing behavior
in terms of intersections, but allows "practical approximations of these
types"). Using the union filtering approach allows us to avoid overly
permissive gradual unions that would result from plain intersections
with a type like `list[Unknown]`. We also extend the union filtering
approach here to properly handle gradual protocols.

In both modes, the negative branch still uses the top materialization to
exclude all specializations.

The new behavior can be summarized like this:

```py
class Invariant[T]:
    value: T

def is_invariant(x: object) -> TypeIs[Invariant]: ...

@final
class Unrelated: ...
```

<table>
  <thead>
    <tr>
<th colspan="3"><code>is_invariant(x)</code> / <code>isinstance(x,
Invariant)</code></th>
    </tr>
    <tr>
      <th>Before narrowing</th>
      <th>Non-strict narrowing</th>
      <th>Strict narrowing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>object</code></td>
      <td><code>Invariant[Unknown]</code></td>
      <td><code>Top[Invariant[Unknown]]</code></td>
    </tr>
    <tr>
      <td><code>Invariant[int] | Unrelated</code></td>
      <td><code>Invariant[int]</code></td>
      <td><code>Invariant[int]</code></td>
    </tr>
  </tbody>
</table>

```py
@runtime_checkable
class Reader(Protocol):
    def read(self) -> Any: ...

class SpecificReader:
    def read(self) -> str: ...

def is_reader(arg: object) -> TypeIs[Reader]: ...
```

<table>
  <thead>
    <tr>
<th colspan="3"><code>is_reader(x)</code> / <code>isinstance(x,
Reader)</code></th>
    </tr>
    <tr>
      <th>Before narrowing</th>
      <th>Non-strict narrowing</th>
      <th>Strict narrowing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>object</code></td>
      <td><code>Reader</code></td>
      <td><code>Top[Reader]</code></td>
    </tr>
    <tr>
      <td><code>SpecificReader | Unrelated</code></td>
      <td><code>SpecificReader</code></td>
      <td><code>SpecificReader</code></td>
    </tr>
  </tbody>
</table>

closes https://github.com/astral-sh/ty/issues/4394 (Fixed in both
non-strict and strict mode; Before: reveals `(list[Unknown] &
~list[Unknown]) | int` and reports `invalid-return-type`, After: reveals
`int`; no errors)

## Ecosystem impact

Looks great. Lots of removed diagnostics in non-strict-mode projects,
and only very few new diagnostics in strict-mode projects, all of which
are expected with these changes here.

## Test strategy

New and updated Markdown tests
2026-09-07 15:58:57 +02:00
David Peter cc85e5d82e [ty] Promote literals in nested (non-covariant) positions (#28389)
## Summary

Promote inferred literals when a type variable occurs inside a
union/intersection/callable in an invariant or contravariant return
position:

```py
def f[T](x: T) -> list[T | None]: ...

reveal_type(f(1))  # now: list[int | None], previously: list[Literal[1] | None]
```

Fixes astral-sh/ty#4468.

## Test Plan

Regression tests
2026-09-07 12:38:57 +00:00
Alex Waygood 519fe8ff08 [ty] Cache exception suppression for context managers (#28376) 2026-09-07 12:48:53 +01:00
Alex Waygood cbb1cdc35e [ty] Dogfood more ty rules on our own scripts (#28387) 2026-09-07 10:04:25 +01:00
Charlie Marsh a1591769ad [ty] Store uncommon signature metadata out of line (#28339)
## Summary

Every `Signature` currently stores optional source overload position and
receiver constraints inline, even though most signatures need neither.
Store those two fields together in an optional boxed payload, reducing
the inline size of the common case. Signatures that need either field
retain its value through mapping, receiver binding, and overload
handling.
2026-09-07 09:36:12 +02:00
Alex Waygood e579b84aba [ty] Intern statement call predicates (#28373)
## Summary

Repeated statement-call reachability checks reconstruct the `(callable,
call_expr, is_await)` key for `analyze_non_terminal_call`. Intern that
information when constructing the predicate and pass its ID directly to
the tracked query, avoiding repeated argument interning. The same fields
continue to identify each call, including awaited calls.

## Performance

Median CPU time across four runs of profiling CLI builds on macOS arm64,
using the existing microbenchmark inputs:

| Benchmark | Before | After | Reduction |
| --- | ---: | ---: | ---: |
| `repeated_statement_calls` | 29.7 ms | 28.0 ms | 5.8% |
| `repeated_statement_calls_pre_narrowed` | 30.4 ms | 28.4 ms | 6.6% |
| `repeated_statement_calls_fixed_reachability` | 29.5 ms | 27.1 ms |
8.1% |
| `repeated_statement_calls_in_try_with_if_branches` | 82.0 ms | 73.6 ms
| 10.3% |
| `repeated_suppressing_context_managers_interleaved_calls` | 68.7 ms |
59.5 ms | 13.4% |

For `repeated_statement_calls`, the retained-memory report falls from
10.36 MB to 8.84 MB (14.6%).
2026-09-06 22:22:15 +01:00
Charlie Marsh cd60f34ffb [ty] Reduce repeated work in use-def merges (#28365)
## Summary

Branch merges repeat reachability work for consecutive places that share
the same last-applied reachability node. We now reuse the most recently
merged path constraint within each merge, where both branch tips remain
fixed. We also skip the member-state merge when there are no member
states.
2026-09-06 16:55:04 -04:00
Charlie Marsh e014dc4dff [ty] Pre-size packed AST index chunks (#28372)
## Summary

Writing the packed AST node index checks and extends its word buffer for
every entry. We now size and zero each chunk's payload once, using its
known entry count and bit width, then write entries through a mutable
slice. Both relative-pointer and wide-pointer layouts retain the same
encoding.
2026-09-06 16:01:42 -04:00
Charlie Marsh 0044db22fa [ty] Avoid unnecessary work when building specializations (#28369)
## Summary

Filling missing generic arguments first constructs fallback types for
every parameter, then overwrites supplied arguments and defaults. We now
fill arguments in one pass, constructing fallbacks only when needed.
Defaults still see the completed prefix of earlier arguments, so
dependent defaults retain their substitution order.

We also skip expanding-cycle detection when there is at most one pending
mapping: self references are not followed, so an expanding cycle
requires at least two mappings.

[CodSpeed
Simulation](https://github.com/astral-sh/ruff/runs/101546864573) reports
+5.37% efficiency for `inherited_recursive_protocol_constructor` and
+5.32% for `inherited_recursive_protocol_diagnostic`, compared with
`0587598`.
2026-09-06 15:55:28 -04:00
Charlie Marsh e0d43d8d39 [ty] Bypass materialization queries for simple types (#28367)
## Summary

Top and bottom materialization currently enter a cached query even when
the type already determines the result. We now handle dynamic types,
divergent cycle markers, simple static types, and nongeneric nominal
instances directly before entering the query.

Types that require traversal continue through the existing cached
materialization and cycle handling. Divergent markers retain their cycle
identity and record the requested materialization.
2026-09-06 15:54:27 -04:00
Charlie Marsh 0587598dec [ty] Store single narrowing operations inline (#28345)
## Summary

We retain two `SmallVec` payloads for every narrowing constraint,
including the common case of a single intersection or replacement
operation. Store single operations inline and allocate the
disjunction-of-conjunctions payload only for combined constraints,
reducing the memory retained by cached narrowing results.

The compact representation preserves the existing merge semantics for
intersections, generic filtering, and `TypeGuard` replacements,
including type-alias normalization when evaluating a constraint.
2026-09-06 12:48:45 -04:00
Charlie Marsh 061ca71cb9 [ty] Box cached MRO error details (#28351)
## Summary

Successful MRO queries retain a result layout large enough to hold the
full error kind and fallback MRO. We now box `StaticMroError` in the
cached `Result`, reducing the retained size of the cached result.
2026-09-06 11:52:59 +00:00
Charlie Marsh 0170bea96b [ty] Reuse generic aliases as MRO query keys (#28350)
## Summary

Resolving a specialized MRO retains an extra Salsa key containing the
class and its specialization. We now key the query directly by the
existing `GenericAlias`, removing the duplicate key storage.

In the retained-memory report, compared with `a2ac770b03`:

| Project | Before | After | Reduction |
| --- | ---: | ---: | ---: |
| Flake8 | 39.03 MiB | 38.94 MiB | 0.21% |
| Sphinx | 172.30 MiB | 171.78 MiB | 0.30% |
| Prefect | 470.90 MiB | 469.40 MiB | 0.32% |
| Trio | 95.86 MiB | 95.51 MiB | 0.36% |
2026-09-06 07:37:56 -04:00
Alex Waygood 4fc78f66fd [ty] Add Literal fixes for diagnostics flagging invalid type annotations (#28185)
## Summary

Add unsafe fixes to diagnostics that suggest `Literal`: wrap integer,
bytes, and boolean literals and strings that cannot be parsed as forward
annotations in `Literal[...]`, and replace parameterized `LiteralString`
with `Literal` when its arguments are valid literal parameters.

The fixes preserve literal spelling and reuse unshadowed import aliases.
They import from `typing` on Python 3.8+, or from `typing_extensions` on
Python 3.7 when it is a declared direct dependency and the installed
module exports `Literal`. The typing-module availability checks are
shared with the `reveal_type` and `assert_never` fixes.
2026-09-06 08:35:58 +00:00
Alex Waygood 5804ffddcd [ty] Add defensive assertion fallback for redundant elif fixes (#28179)
When the final `elif` condition is always true but ty cannot add an
`assert_never` call, offer an unsafe fix for
`redundant-condition-strict` that replaces the `elif` with `else` and
moves the condition into a defensive assertion.

For example, on Python 3.10 in a project without a direct dependency on
`typing_extensions`, the fix makes this change:

```diff
 def handle(value: str | int):
     if isinstance(value, str):
         print(value)
-    elif isinstance(value, int):
+    else:
+        assert isinstance(value, int)
         print(value)
```

After the `str` branch, `value` must be an `int`, so the final
`isinstance` check is statically always true. The assertion retains that
check at runtime while making the exhaustive branching explicit.

The fallback also applies when the tested value cannot be reused as an
`assert_never` argument or import-name conflicts prevent a usable
import. It preserves condition parentheses, comments, indentation, and
line endings, and parenthesizes bare assignment expressions. No edit is
offered when the body cannot accommodate an indented assertion or
rewriting the header would discard a comment.

The fix is unsafe because an incorrect static assumption can make the
assertion fail at runtime.

Stacked on #28178.
2026-09-06 08:35:57 +00:00
Charlie Marsh a2ac770b03 [ty] Reuse interned bound method signatures (#28346)
## Summary

Converting a bound method to a callable retains its signatures in both
the `bound_signatures` query and the interned `CallableType`. We now
compute the bound signatures in `into_callable_type` and have
`bound_signatures` borrow the interned signatures, removing the extra
query and retained signature copy.
2026-09-05 21:44:22 -04:00
Charlie Marsh 480ff56d17 [ty] Reuse stored function signatures without caching duplicates (#28347)
## Summary

We currently clone an updated signature out of `FunctionType` and retain
a second copy in the Salsa cache whenever it is requested. Return the
stored signature directly, avoiding the duplicate allocation and memo
entry.

Signatures derived from the function literal still use a tracked query,
so changes to the defining AST only invalidate callers when the computed
signature changes.
2026-09-05 21:43:33 -04:00