Skip to content

catch-up: merge astral-sh/ruff main downstream into fork (baseline, not for merge) - #100

Merged
AdaWorldAPI merged 1519 commits into
mainfrom
claude/upstream-catchup-2026-08-18
Aug 18, 2026
Merged

catch-up: merge astral-sh/ruff main downstream into fork (baseline, not for merge)#100
AdaWorldAPI merged 1519 commits into
mainfrom
claude/upstream-catchup-2026-08-18

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Purpose

Safety-net baseline, not a merge candidate as-is. This PR exists so real CI (full workspace tests across Linux/Windows/macOS/wasm, the ecosystem job, clippy, fmt) exercises the catch-up merge that local scoped checks couldn't fully cover — not to be merged into main without further review. Kept as a standalone reference per the earlier "keep a copy in another branch" direction; this draft is the validation step on top of that, not a change of intent.

What this is

upstream/main (astral-sh/ruff) merged downstream into this fork's main — the fork stays the mainline (first parent), upstream's 1513 commits since the merge-base (8c930f5972841d899ff0586f9b8d45a85815a5d3) come in as the second parent via a real merge commit, not a rebase. 313 fork-only commits since that point are preserved untouched.

This closes both "legitimate feature port" gaps flagged earlier without hand-porting anything: RUF105 and the human-readable rule-name selector machinery are now present, pulled in as part of the merge.

Conflict resolution (11 files had real conflicts; ~26 more in the overlap surface auto-merged clean)

Each was traced to the fork's own commit history before choosing a side — never guessed:

  • AGENTS.md — upstream's file, fork's SPO/transcode paragraph re-inserted after the intro.
  • Cargo.toml — union: upstream's dependency bumps + every fork-only path dep/exclude/patch kept.
  • Cargo.lock — regenerated via cargo generate-lockfile, not hand-resolved.
  • crates/ruff/src/lib.rs, crates/ruff_linter/src/rules/ruff/rules/sort_dunder_slots.rs, crates/ruff_server/tests/e2e/main.rs, crates/ty_server/tests/e2e/main.rs, crates/ty_python_semantic/src/types/narrow.rs, crates/ty_python_semantic/src/types/protocol_class.rs, crates/ruff_python_formatter/src/range.rs — took upstream's side; each verified semantically identical to the fork's own colliding clippy-1.97-sweep change via git show on the fork's commit before deciding.
  • crates/ruff_annotate_snippets/src/renderer/display_list.rs — modify/delete, followed upstream's restructure (vendored newer annotate-snippets).
  • .github/workflows/ci.yaml, .pre-commit-config.yaml — auto-merged clean; hand-verified the fork's deliberate additions (Windows ruff_cpp_spo libclang-crash exclusion from ci: pull upstream ecosystem-ref fixes (bokeh, jrnl-org) + fork-side Windows libclang exclude #99; the pre-commit fixer-hook exclusions added after real data corruption) survived and grafted onto upstream's current file shape.
  • python/ruff-ecosystem/ruff_ecosystem/defaults.py, scripts/check_ecosystem.py — no-op (fork's only change was the same two upstream ref-bump cherry-picks already in ci: pull upstream ecosystem-ref fixes (bokeh, jrnl-org) + fork-side Windows libclang exclude #99).
  • rust-toolchain.toml — no conflict, both sides already 1.97.1.

Validation so far (local, scoped — this PR's CI is the real test)

  • RUF105 / is_human_readable_names_enabled confirmed present post-merge.
  • cargo check -p <fork's own crates> -p ty -p ruff — clean.
  • cargo clippy on the same scope — 1 pre-existing failure (iter_over_hash_type, crates/ruff_ruby_spo/src/menu_regions.rs:2208), confirmed via diff the merge touched zero files in that crate — prior fork debt, not introduced here, left untouched.
  • cargo fmt --check on the same scope — clean.
  • Full-workspace cargo check/test was not run locally (too slow for 1512 commits of drift) — deferred to this PR's CI.
  • ruff_r2il could not be validated at all locally (worktree-sandbox path collision, unrelated to the merge) — this PR's CI is the first real check for that crate.

Known follow-ups, not blocking this PR

  1. ruff_r2il needs a real build check from a non-nested checkout (blocked locally by sandbox, not by the merge).
  2. The lsp-types git patch/vendor directory is now dead weight — upstream moved to a published gen-lsp-types crate. Prints a harmless unused patch warning; candidate for a separate cleanup PR, not touched here.
  3. The pre-existing ruff_ruby_spo clippy lint above, unrelated to this merge.

Test plan

  • git log --graph confirms correct merge topology (fork main as first parent)
  • Scoped local build/clippy/fmt (see above)
  • CI on this PR — the actual falsifier for the parts local checks couldn't cover

Generated by Claude Code

dhruvmanila and others added 30 commits August 4, 2026 06:51
…l-sh#27431)

## Summary

Preserve callable return-type constraints when matching `Callable[...,
T]` against callbacks with `*args: object` and `**kwargs: object`.

fixes: astral-sh/ty#4151
fixes: astral-sh/ty#4169

## Test plan

Add a focused regression for return-type inference through
object-variadic callbacks.

Verified the original issue reproduction now infers `ChildClass` for the
decorated method result.
## Summary

Preserve return-type inference for `Top[Callable[..., T]]`.

Follow-up to
astral-sh#27431 (comment).

## Test plan

Cover return-type inference for an ordinary callback accepted as a top
callable.
## Summary

Bump both ty ecosystem workflows to astral-sh/ecosystem-analyzer#143,
which includes hauntsaninja/mypy_primer#257, which fixes `attrs` and
`Bokeh`.
## Summary

Top-materialize th gradual `DataclassInstance` protocol in the `TypeIs`
return type of `is_dataclass`, so that we can recognize dataclass
instances as being a subtype of that return type.

Fixes astral-sh/ty#4149.

## Ecosystem impact

Looks good!

## Test plan

Updated and added mdtests.
…7458)

## Summary

Reject unrecognized `dataclass_transform` parameters based on this
paragraph in the
[spec](https://typing.python.org/en/latest/spec/dataclasses.html#dataclass-transform-parameters)
(emphasis mine):

> kwargs allows arbitrary additional keyword args to be passed to
dataclass_transform. This gives type checkers the freedom to support
experimental parameters without needing to wait for changes in
typing.py. **Type checkers should report errors for any unrecognized
parameters.**

Since we currently don't do any experiments with unofficial parameters
to `dataclass_transform`, we can just remove `**kwargs`.

Fixes astral-sh/ty#4170.

## Test plan

New Markdown tests.
## Summary

OR-pattern alternatives can bind the same names but "execute" mutually
exclusively. The semantic index previously visited alternatives as
consecutive assignments, so later captures shadowed earlier captures and
caused the language server to incorrectly report those earlier bindings
as unused.

Visit each capture-bearing alternative from the same incoming flow state
and merge the resulting bindings. Preserve the existing fast path for OR
patterns that do not bind names.

Closes astral-sh/ty#4163.

## Test plan

- Used captures across multiple OR-pattern alternatives and captured
names.
- Nested OR-pattern captures referenced only by a match guard.
- Genuinely unused alternative captures, earlier shadowed assignments,
and captures in separate match cases.
…stral-sh#27340)

## Summary

Constructing a generic class from one of its own type variables
currently loses that type variable and produces `C[Unknown]`. We now
freshen the constructor-specific generic context while preserving the
source-level return template, so `C(value)` within `C[T]` retains
`C[T]`.

We also keep bound constructor receivers and downstream `__init__`
bindings on the same fresh occurrence, and distinguish that occurrence
from outer type variables during inference.

Closes astral-sh/ty#4132.

Closes astral-sh/ty#3963.
)

## Summary

We now exclude private type variables, parameter specifications,
type-variable tuples, aliases, and `@type_check_only` definitions from
completions, even though those names can be explicitly imported in
typing-only contexts:

```python
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from package import _Alias, _T
```

This PR makes those definitions consistent with existing
`@type_check_only` completion behavior: retain them as explicit import
and attribute completions, mark them as typing-only, and rank them below
runtime values.
…sh#27453)

## Summary

On Python 3.14, deferred annotations allow a function to reference a
`TypeVar` declared after calls to that function:

```python
class C: ...

def f(a: T): ...

if f():
    pass

if f():
    sum()
else:
    sum()

from typing import TypeVar

T = TypeVar("T", default=C)
```

Previously, resolving `T`'s default during generic specialization
re-entered the same Salsa query through reachability analysis, causing a
dependency-cycle panic.

We now initialize specialization cycle recovery with unknown type
arguments, allowing Salsa to complete the surrounding inference without
evaluating the default recursively. Once inference stabilizes, the
actual default remains `C` and we report the expected argument and
overload diagnostics.

Closes astral-sh/ty#4174.
…tral-sh#27459)

This PR moves the benchmarks that exercise our constraint set
implementation into their own file. This is a refactoring pulled out of
astral-sh#27337. The source file is included in Codspeed's benchmark ID, so
moving them like this disconnects these benchmarks from their history.
Doing the move in a separate PR means that astral-sh#27337 will at least show an
accurate delta for the existing benchmarks relative to `main`.
…res (astral-sh#27436)

## Summary

Prior to this change, constructing a bare generic `TypedDict` discarded
the constraints provided by its fields:

```py
from typing import TypedDict

class Box[T](TypedDict):
    value: T

reveal_type(Box(value=1))  # Box[Unknown]
```

This PR makes the synthesized `TypedDict.__init__` generic, so direct
keyword calls use the same overload matching, contextual inference, and
constraint solver as other generic constructors.

For now, we only support direct keyword calls, as in:

```py
class Pair[T](TypedDict):
    first: T
    second: T

reveal_type(Box(value=1))  # Box[int]
reveal_type(Pair(first=1, second="x"))  # Pair[int | str]
```

Positional mappings and dictionary unpacking are deferred... their field
constraints can't yet be propagated through ordinary constructor binding
without losing key-specific information or mishandling overwrites:

```py
reveal_type(Box({"value": 1}))  # Box[Unknown]
reveal_type(Box(**{"value": 1}))  # Box[Unknown]
```

Nested generic `TypedDict` fields are also deferred until their
constraints can be propagated soundly. Recursive construction remains
valid, but retains its existing gradual specialization:

```py
from typing import NotRequired

class Node[T](TypedDict):
    value: NotRequired[T]
    child: NotRequired["Node[T]"]

reveal_type(Node(child=Node(value=1)))  # Node[Unknown]
```

Closes astral-sh/ty#4134.
astral-sh#27463)

<!--
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 renames an internal helper function to be slightly more descriptive
of the predicate it encapsulates.

## Test Plan

This is a simple rename that relies on existing test coverage.
<!-- How was it tested? -->
…l-sh#27442)

## Summary

Generic method decorators and descriptor constructors could erase
enclosing class type parameters when callable-signature comparison
expanded implicit `Self` bounds and existentially quantified the entire
inferable set. This caused decorated methods to return `Unknown` and
produced false-positive diagnostics for generic `cached_property`
methods.

Preserve enclosing class variables during lazy comparisons while
retaining expanded inference for eager unbound-method comparisons,
including inside generic higher-order calls. Generic cached-property
protocol members now expose their correctly specialized readable and
writable types.

Fixes astral-sh/ty#4153.
Fixes astral-sh/ty#3256.

## Test plan

- Added mdtests for PEP 695 and legacy generic method decorators,
including nested `list[T]` return types.
- Added mdtests for generic `cached_property` methods with direct and
union return types, and updated generic protocol descriptor
specialization coverage.
- Added mdtests for generic higher-order functions receiving unbound
generic methods, including `functools.reduce(set.union, ...)`.
- Added a constraint-level regression while preserving existing
unbound-method assignability coverage.
…-sh#27460)

## Summary

Previously, we only recovered a generic constructor's specialization
from an overload that passed argument validation. If validation failed,
we discarded any type arguments the binding had already inferred,
allowing the class's internal type variables to escape into the
constructed value.

See here, where we emit two diagnostics on `consumer: Consumer[Animal] =
Consumer(accepts_dog)`:

```py
from collections.abc import Callable

class Animal: ...
class Dog(Animal): ...

class Consumer[T]:
    def __init__(self, callback: Callable[[T], None]) -> None:
        self.callback = callback

def accepts_dog(value: Dog) -> None: ...

# error: [invalid-assignment] Object of type `Consumer[T@Consumer]` is not assignable to `Consumer[Animal]`
# error: [invalid-argument-type] Argument to `Consumer.__init__` is incorrect: Expected `(Animal, /) -> None`, found `def accepts_dog(value: Dog) -> None`
consumer: Consumer[Animal] = Consumer(accepts_dog)
```

The callback error is correct: `accepts_dog` cannot handle every
`Animal`. But the constructor binding had already established `T =
Animal` from its surrounding context. Discarding that specialization
returned `Consumer[T@Consumer]` and produced a second, misleading
assignment error.

We now preserve the specialization already established by constructor
binding, even when argument validation fails, so only the callback error
remains. The net effect is that we show fewer redundant errors and fewer
cascading errors.
## Summary

Teach the `SymbolVisitor` to recognize names introduced by structural
pattern matching.

This handles:
- capture and `as` patterns;
- sequence and starred patterns;
- mapping patterns, including `**rest`;
- positional and keyword class patterns;
- nested patterns and `|` alternatives;
- module-level bindings as variables or constants;
- class-level bindings as fields.

Function-local bindings remain excluded, consistently with regular
assignments. Pattern bindings, guards, and case bodies retain
source-order traversal.

Addresses the `case bar:` item in astral-sh/ty#1771.

> [!NOTE]
> This draft has been realigned on top of astral-sh#27256, which now provides the
generic Store-context handling. This PR now contains only the
match-pattern binding support.

## Test Plan

- `cargo test -p ty_ide`
- `cargo clippy -p ty_ide --all-targets --all-features -- -D warnings`
- `uv run --only-group dev --locked prek run --files
crates/ty_ide/src/symbols.rs crates/ty_ide/src/document_symbols.rs`

---------

Co-authored-by: Lérè <contact@lrbr.dev>
## Summary

Previously when we reported the intersection type that a constructor
call tried to call, we synthesized an intersection of the constructor
methods (e.g. `__init__`, `__new__`, or metaclass `__call__`). Since
these are often bound methods, which are disjoint from other bound
methods, this synthesized intersection might resolve to `Never`, leading
to a confusing sub-diagnostic claiming that we had tried to call the
intersection `Never`.

Instead, preserve each union element's called type while constructing
and transforming bindings, so intersection diagnostics report the
original class types instead of collapsing constructor method signature
intersections into `Never`.

## Test plan

- Add snapshot-backed mdtests for standalone constructor intersections
and constructor intersections nested inside unions.
- Cover excluded types in layered intersection diagnostics and
single-callable union variants.
…h#27449)

## Summary

This PR validates `type[T]` against `T`'s upper-bound, but leaves bare
`type` permissive:

```py
from collections.abc import Callable

def permissive(cls: type) -> None:
    cls(1)  # still accepted

def unbounded[T](cls: type[T]) -> T:
    zero_argument: Callable[[], T] = cls
    one_argument: Callable[[int], T] = cls  # error: [invalid-assignment]
    return cls(1)  # error: [too-many-positional-arguments]

def object_bound[T: object](cls: type[T]) -> T:
    return cls(1)  # error: [too-many-positional-arguments]
```

I believe this is both consistent with the [constructor typing
specification](https://typing.python.org/en/latest/spec/constructors.html#constructor-calls-for-type-t)
and gets us passing the relevant conformance tests.

The background is that in astral-sh#24357, we brought constructor handling much
closer to the typing specification by respecting `__new__` and metaclass
`__call__` return types, but `constructors_call_type.py` still had one
false negative (calling an unbounded `type[T]` with arguments).

We then put up astral-sh#23514 which addressed that case by overriding the
permissive typeshed signature for `type.__call__`, making bare `type`,
`type[object]`, and unbounded `type[T]` all use `object`'s zero-argument
constructor. That created substantial ecosystem fallout and added
conformance false-positives. @carljm suggested that, if we eventually
want stricter `type[object]`, we should instead distinguish it from bare
`type` instead of globally replacing `type.__call__`.
…stral-sh#27493)

## Summary

In astral-sh#27449, we start treating calls to `type[object]` strictly (using
`object.__init__` rather than the overly forgiving `type.__call__`).
This also means that `type[T] & type[SomeType]` intersections (which can
easily arise with a parameter `cls: type[T]` which is then narrowed via
`issubclass(cls, SomeType)`) start validating the `type[T]` portion
against `object.__init__` (assuming `T` has no upper bound besides
`object`). That means if the call to `type[SomeType]` fails, we now get
confusing double diagnostics also complaining about the failed call to
`object.__init__`.

This PR resolves that problem separately in a general way: if we have an
intersection `type[T] & type[SomeClass]`, where `SomeClass` is a
subclass of the upper bound of `T`, we don't try calling both
constructors; we just try the constructor of `SomeClass`. (But we
preserve the full intersection as receiver, so that the result of the
call will still be `T & SomeClass`, not just `SomeClass`.) Since all
classes are subclasses of `object`, this means we don't try calling
`object.__init__` or emit errors about it when they are redundant and
confusing in an intersection with some more specific `type[...]`.

(This is only relevant to intersections with `type[T]` where `T` is a
typevar, since a normal `type[Base] & type[Child]` intersection would
immediately simplify to `type[Child]`. That simplification doesn't occur
with a typevar, since we need to preserve the typevar identity; it may
actually represent a narrower type, not its upper bound.)

- Resolve constructor calls on narrowed `type[T] & type[Child]`
intersections using the applicable subclass constructor instead of
treating the type-variable bound as an independent alternative.
- Preserve the precise `T & Child` result, report only
subclass-constructor argument errors, and retain specialized generic
constructors, built-in behavior, independent metaclass callables, and
explicit `__new__` / metaclass `__call__` return types.

## Test plan

- Added constructor mdtests for `issubclass`-narrowed bounded,
constrained, and unbounded type variables; valid return types; rejected
arguments; and deduplicated diagnostics.
- Covered explicitly specialized generic constructors,
literal-preserving built-in constructors, `Self` returns, non-instance
`__new__` and metaclass `__call__` returns, and intersections with
independent metaclass callables.

Ecosystem changes are both correct/improvements.
charliermarsh and others added 19 commits August 15, 2026 17:39
…tral-sh#27790)

## Summary

Move implicit-attribute inference out of the large static-class
implementation and into `types/class/implicit_attributes.rs`.
…al-sh#26693)

## Summary

Preserve source ranges for PEP 723 script metadata by building a compact
source map during extraction and applying it when deserializing ranged
values.

Closes astral-sh/ty#4180
<!--
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

Adds explicit 7d cooldowns to all of our PEP 723 scripts, as well as
similar cooldowns via `.npmrc`.

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

## Test Plan

NFC.

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


---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
<!--
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

Addresses all `uv audit` findings.

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

## Test Plan

NFC.

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

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
Signed-off-by: William Woodruff <william@yossarian.net>
…rsive queries (astral-sh#27737)

## Summary

This fixes a "too many iterations" panic I saw when fixing
astral-sh/ty#4246. This panic reproduces on main already with a slightly
different example, so fix it first here. Irrelevant quantified-away
constraints can pollute a source-ordering sidecar and cause fixpoint to
never converge.

- Drop quantified-away constraints from persisted source ordering when
their complete support is unrelated to the remaining live constraint
set. This prevents fresh type variables from keeping recursive Salsa
queries from reaching a fixed point.
- Preserve related quantified constraints and conservatively retain
entries whenever either support is incomplete.
- Stop collecting type-variable declaration bounds, value constraints,
and defaults as constraint support, so unrelated eager metadata does not
add dependencies and lazy metadata does not incorrectly mark support
incomplete.

Related to astral-sh/ty#4246 and astral-sh#27732.

## Test plan

- Add an overloaded generic-protocol classmethod mdtest that reproduces
`too many cycle iterations` on main and verifies both receiver-bound
overload signatures.
- Add separate protocol receiver-binding mdtests for a bounded legacy
type variable and a defaulted PEP 695 method type variable.
- Cover removal of unrelated quantified constraints and preservation of
ordering for related quantified constraints with focused constraint-set
unit tests.
- Cover eager type-variable defaults and lazy declaration bounds, value
constraints, and defaults while preserving type variables actually
present in structural constraint bounds.
…#27786)

A nested class or comprehension inside a function could infer only the
conditionally reassigned value of a `global`, dropping its existing
module-level value. This let ty accept code that could fail at runtime,
and could cause false-positive `possibly-unresolved-reference` errors.

Resolve forwarded global snapshots against real module-level bindings
and declarations before falling back to implicit globals or builtins.
Exclude synthetic bindings from nested `global` assignments so existing
implicit-global and builtin behavior is preserved.

Closes astral-sh/ty#4273.

## Test plan

Added scope mdtests covering:

- A nested class reading a global with an existing module-level binding
after conditional reassignment.
- A nested class reading a global that has only a module-level type
declaration.
- An eager comprehension reading a conditionally reassigned global.

Existing neighboring mdtests also cover fallback to implicit globals and
builtins.
We currently ignore concrete upper bounds when inferring a gradual
solution. We should instead be intersecting the gradual type with its
upper bound, e.g.,

```py
from typing import Any, Callable

def infer[T](lower: T, upper: Callable[[T], None]) -> T:
    return lower

def _(x: Any, upper: Callable[[int], None]):
    reveal_type(infer(x, upper))  # revealed: int & Any
```
…sh#27732)

## What was the problem?

Checking an expression such as `pd.Series([1.0]) + np.array([1.0])`
could hang indefinitely. The Series already determines one of the
addition method's type variables, but ty tried to infer it together with
the remaining arguments. That left too many possibilities to consider
across NumPy's many operator overloads.

## How does this fix it?

When a method explicitly annotates `self` or `cls`, first determine any
method type variables fixed by that object. Use those known types when
checking the remaining arguments and describing the bound method, while
leaving unrelated variables available for normal argument inference.

Only do this extra work when the variable also appears in another
parameter or the return type. Variables used only in the receiver,
variables belonging to the class, and `typing.Self` cannot help with
argument checking. Skipping them avoids unnecessary work and preserves
performance on `DateType` and `hydra-zen`. Type aliases are handled
without expanding their definitions.

Fixes astral-sh/ty#4246.

## Test plan

- Cover a generic method where the receiver determines one type variable
and another argument determines a separate variable.
- Cover type aliases in receiver annotations, return types, and other
parameters.
- Cover type variables used only in the receiver, which should remain
unspecialized.
- Verify pandas Series addition and multiplication with NumPy arrays,
with no performance regressions on `DateType` or `hydra-zen`.
…tral-sh#27703)

## Summary

When analyzing a `try` block, we record which bindings an exception
handler could see before each potentially raising operation. In a large
function, copying and later merging these snapshots can be expensive.

We already reuse checkpoints for consecutive calls when nothing relevant
changes. However, harmless control flow can defeat that optimization:

```python
def example(value: str, flag: bool) -> None:
    try:
        value.upper()
        if flag is True:
            pass
        value.upper()
    except Exception:
        pass
```

The `if` introduces branches, but after they rejoin, the second call
exposes the same bindings to the handler. Previously, restoring or
merging a branch always advanced the checkpoint's control-flow revision,
so we retained another snapshot. We now recognize equivalent paths and
reuse the checkpoint.

Equivalent control flow does not necessarily mean equivalent bindings:
nested exception handlers can merge paths with different possible
values. To preserve all of those values, we track a restorable identity
for the visible bindings as well as reachability, giving distinct merged
states a fresh identity. We also preserve the call history needed to
handle caught `NoReturn` calls correctly. If the reachability graph
reaches its size limit, we fall back to conservative deduplication
rather than retaining a snapshot for every subsequent call.

Two supporting changes avoid redundant work when combining a
reachability condition with itself and keep the larger flow snapshots
out of the common recursive expression-visitor stack frame.

This is complementary to astral-sh#27787: that change makes evaluating narrowing
and reachability histories cheaper, while this change avoids
constructing redundant exception-state snapshots.

## Performance

On a synthetic workload with 800 locals and 800 calls separated by
equivalent `if` branches, this reduces CPU time by about 26% and peak
memory by 59%. The ordinary-call control and the suppression-heavy
workload are essentially unchanged:

| Workload | CPU, before → after | Peak RSS, before → after |
| --- | ---: | ---: |
| Plain repeated calls | 148 → 148 ms | 123.4 → 123.3 MiB |
| Calls separated by equivalent `if` branches | 268 → 199 ms | 102.5 →
42.1 MiB |
| Conditional assignments under suppression | 819 → 816 ms | 111.2 →
109.9 MiB |
On Python 3.12, aliases declared with `type Alias = int` are instances
of `typing.TypeAliasType`, but ty currently infers them as instances of
`typing_extensions.TypeAliasType` because both classes share a
`KnownClass` variant that resolves to the backport.

- Distinguish the standard-library and backport `TypeAliasType` classes
with separate `KnownClass` variants.
- Generalize `TypedDictModule` into a shared `TypingModule` enum and
retain the actual constructor origin on manually created aliases through
specialization and materialization.
- Update alias construction, runtime-class fallback, TypedDict handling,
and IDE argument classification to use the correct module.
- Cover statement-defined aliases and both direct constructors on Python
3.12, and update `__type_params__` inference to reflect the
standard-library class.

The fact that we fail to model this precisely right now appears to cause
a surprising number of ecosystem diagnostics on bokeh.
Lazy protocol checks currently replace `type[T]` and `type[Any]` with
plain `type`. This loses the class object's interface before generic
inference can use it. For example, collecting an iterable class together
with an empty fallback can lose `Self` and infer `Unknown` instead.

Allow lazy assignability to use the same structural protocol check as
eager assignability. Strict subtyping keeps its existing behavior.

This is a prerequisite for astral-sh#27812 and is related to astral-sh/ty#4291.

## Test plan

- Add a public mdtest using a generic metaclass iterator and a
classmethod that collects `frozenset[Self]` through an empty fallback.
- Cover a gradual class object combined with a concrete iterable,
ensuring the concrete element type still contributes to inference.
An annotated assignment could lose its declared type in an exception
handler, when the path to the exception handler is through an exception
evaluating its right-hand side. Assignments in the handler were then
inferred without that type context, so a fallback assignment could
acquire an incompatible value type, without the benefit of type context
from the declared type.

This is arguably "expected behavior" in our flow-sensitive
declared-types model, but intuitively it feels like the declaration
should "take effect" before the RHS "executes". Modeling it that way
requires a bit of new machinery in the use-def map to allow splitting
the declaration and binding from a single assignment in control flow,
but it's not too bad and even allows some simplifications.

Record the declaration before visiting the annotation and right-hand
side, then record the value binding only after the right-hand side
completes. The two control-flow entries retain their execution order,
but only the binding participates in usage analysis. Keeping usage state
in those entries also removes the parallel usage vector. This preserves
ty's existing exception-point model.

Fixes astral-sh/ty#4293.

## Test plan

- Exception-flow mdtests cover contextual dictionary inference,
incompatible fallback assignments, previous or unbound values,
reannotations, declarations after an earlier exception checkpoint, and
assignments made inside the right-hand side.
- Unused-binding tests cover annotated loop-carried values, shadowed
annotated bindings, and later bindings captured by closures.
…to 2026-08-18

Upstream catch-up merge: brings this fork's history (merge-base
8c930f5) forward by 1512 upstream commits,
alongside the fork's own 313 commits since that point. This is kept as a
separate integration branch (claude/upstream-catchup-2026-08-18) and is not
merged into the fork's main branch.

Two upstream features arrive as part of this catch-up, no separate porting
needed:
  - RUF105 (invalid rule code in `noqa` comment)
  - Human-readable rule names in selectors (`is_human_readable_names_enabled`,
    `Rule::from_name`, `UnresolvedRuleSelector::resolve`)

Real conflicts (37 files overlapped fork vs. upstream changes; most were
either the fork's own mechanical clippy-1.97 sweep colliding with upstream's
own independent mechanical simplifications at the same lines, or a genuine
union merge):

  - AGENTS.md: took upstream's full rewritten file, re-inserted the fork's
    SPO/transcode-crate paragraph after the intro.
  - Cargo.toml: union merge — adopted upstream's per-crate `version = "0.0.9"`
    style workspace.dependencies entries, kept the fork-only path deps
    (ruff_cpp_codegen, ruff_spo_triplet), kept the fork's `exclude =
    ["crates/ruff_r2il"]` and the local lsp-types vendor patch (now inert
    since upstream moved lsp-types off its git dependency onto a published
    crate, but harmless — cargo just warns, doesn't fail).
  - Cargo.lock: regenerated via `cargo generate-lockfile` from the merged
    Cargo.toml rather than hand-resolved.
  - crates/ruff/src/lib.rs, crates/ruff_linter/.../sort_dunder_slots.rs,
    crates/ruff_server/tests/e2e/main.rs, crates/ty_server/tests/e2e/main.rs,
    crates/ty_python_semantic/src/types/narrow.rs: took upstream's side —
    fork's only change at each conflict site was a mechanical clippy-1.97
    `question_mark`/style fix, semantically identical to upstream's own
    independent simplification.
  - crates/ty_python_semantic/src/types/protocol_class.rs: took upstream's
    side — fork's only change nearby was stripping redundant `&` borrows in
    `format_args!`; upstream had independently restructured the whole
    `display` method for unrelated reasons.
  - crates/ruff_python_formatter/src/range.rs: took upstream's side —
    verified the fork's `#[expect(clippy::question_mark, ...)]` preserved
    style was semantically identical to upstream's `?`-based rewrite (both
    already returned None in the missing-indentation case).
  - crates/ruff_annotate_snippets/src/renderer/display_list.rs: modify/delete
    conflict; upstream deleted the file as part of vendoring a newer
    annotate-snippets (astral-sh#27033, restructured into render.rs etc.); the fork's
    only change was a mechanical clippy fix, so followed upstream's deletion.
  - .github/workflows/ci.yaml, .pre-commit-config.yaml,
    python/ruff-ecosystem/ruff_ecosystem/defaults.py, scripts/check_ecosystem.py:
    auto-merged cleanly by git with no conflict markers; verified by hand
    that the fork's deliberate additions (the ruff_cpp_spo Windows
    libclang-crash exclusion in ci.yaml; the vendor/.claude/dto_check golden
    exclusions plus the mangled-fixer-hook-data comment in
    .pre-commit-config.yaml) survived intact and correctly grafted onto
    upstream's current file structure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AGVLyRZNEKKBSfBDJfbY3V
@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_708dc700-c08c-49fd-8353-e4cf093900cd)

@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review August 18, 2026 07:36
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

claude added 6 commits August 18, 2026 07:40
Upstream (pulled in by this catch-up merge) no longer depends on
lsp-types via git at all — it now uses the published gen-lsp-types
crate (see the `lsp-types = { package = "gen-lsp-types", ... }`
alias already in Cargo.toml). The fork's `[patch]` block still
pointed cargo at a git URL nothing depends on anymore, redirected to
a gitignored, never-committed `vendor/lsp-types/` path.

Locally this was silent ("unused patch" warning). In the Docker
build (a fresh container with no locally-regenerated vendor dir) it
was fatal: cargo still probes every `[patch]` source during manifest
resolution regardless of whether anything in the graph uses it, so
`cargo zigbuild` failed trying to read a Cargo.toml that was never
part of the repo.

Removed the patch block, the vendor/ gitignore entry, and the
dev-unblock-lsp-types.sh script that generated it (nothing else in
the tree references the old git source — checked). Cargo.lock
regenerated; only the dead git-source lock entry (4 lines) dropped,
gen-lsp-types resolves unchanged.
Pre-existing lint debt, exposed by real CI running full-workspace
clippy on this catch-up branch for the first time (confirmed via
`git diff` that the surrounding merge touched zero files in this
crate — this is not a merge-introduced regression).

The loop iterates a HashMap<String, (String, String)> and checks
each entry independently against only its own value plus a running
count (`parentless_checked`); no assertion in the body depends on
which order entries are visited. Per AGENTS.md's own guidance,
#[expect(...)] with a reason beats a blanket #[allow] here since
this is a real, order-independent iteration rather than dead code.
Real CI on the catch-up PR (#100) surfaced two findings on the
Windows ruff_cpp_spo exclusion added in #99, neither of which the
local prek gate could catch at the time (zizmor's own audit needs
outbound GitHub API access this sandbox's proxy denies — confirmed
403, same failure mode either way):

- zizmor: medium-severity template-injection finding — interpolating
  `${{ matrix.platform }}` directly into a `run:` shell block
  substitutes before bash ever sees the script, so a value containing
  shell metacharacters could break out of the string context. Has a
  documented auto-fix: pass through `env:` instead.
- actionlint/shellcheck SC2193 on the same line, which the env-var
  form also resolves (shellcheck sees a normal `"$VAR" == pattern`
  comparison rather than a literal it can statically reason wrongly
  about).

matrix.platform itself is not attacker-controlled here (a static
2-value list gated on `github.repository`), but the fix is correct
regardless of that — env-var passing is the general mitigation for
this class of finding, not a per-instance judgment call.
Real CI's prek gate (a required check) surfaced this consistently on
this catch-up branch: AGENTS.md uses asterisk emphasis everywhere
except this one line, which markdownlint-fix flags and rewrites.
Confirmed genuine before accepting — grep shows it's the sole
underscore-emphasis line in an otherwise asterisk-emphasis file, so
this fixes a real (harmless, upstream-inherited) inconsistency rather
than introducing fork drift from upstream's style.
Real CI on the catch-up PR surfaced this on both docker-build matrix
legs: `ghcr.io/${{ github.repository_owner }}/ruff` resolves to
`ghcr.io/AdaWorldAPI/ruff` on this fork, and OCI registries reject
any mixed-case repository name outright ("failed to parse ref ...:
repository name ... must be lowercase"). Upstream never hits this —
`astral-sh` is already all-lowercase.

The workflow-level `env:` can't lowercase inline (no `lower()` in
GitHub Actions expression syntax), so each of the four jobs
(docker-build / docker-publish / docker-publish-extra /
docker-republish) gets a first step that recomputes RUFF_BASE_IMG via
bash's `${VAR,,}` into $GITHUB_ENV, overriding the workflow-level
fallback for the rest of that job before any step actually
builds/tags/pushes an image reference.

Only docker-build is exercised by this PR (the other three jobs gate
on `inputs.plan`, which is unset outside an actual release run via
workflow_call) — fixed all four anyway since a real release attempt
on this fork would hit the identical failure otherwise.
Root cause of the two stuck CI runs on this catch-up PR (both attempts
hung with `PGO` queued 40+ minutes, no runner ever claimed it, and
several other required jobs got cancelled alongside it before the
run could reach a clean terminal state for a failed-jobs rerun):

`pgo`'s `runs-on: depot-ubuntu-24.04-8` was hardcoded with no fork
fallback — unique among this file's other three bare `depot-*` jobs,
which all correctly gate their own `if:` on
`github.repository == 'astral-sh/ruff'` so they're skipped entirely
on a fork (confirmed via their own comments/conditions). `pgo`'s `if:`
only checks `determine_changes.outputs.release`, so it actually
attempts to run on this fork — and `depot-*` are Depot.dev custom
runners registered only for the upstream org, so nothing can ever
claim that label here.

Applied the same `github.repository == 'astral-sh/ruff' && '...' ||
'ubuntu-latest'` fallback every other conditionally-depot job in this
file already uses.
@cursor

cursor Bot commented Aug 18, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_63f12996-baba-4e52-9262-7362598ed443)

@AdaWorldAPI
AdaWorldAPI merged commit ca33155 into main Aug 18, 2026
49 of 68 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.