Files
Carl Meyer 124210f4fd [ty] Preserve inherited value types on repeated bare ClassVar annotations (#30396)
A subclass can give a class variable from the base class a new default,
and it can annotate that assignment with bare `ClassVar`. Previously, we
rejected `Record` below because it inferred a one-element tuple for
`group_by`, which we considered an `invalid-mutable-override` (narrowing
the type of a mutable attribute), and then we also rejected the
subsequent assignment to `Record.group_by` as incompatible with the
inferred one-element tuple type.

```python
from typing import ClassVar

class Message:
    group_by: ClassVar[tuple[str, ...]] = ()

class Record(Message):
    # Previously we inferred `ClassVar[tuple[str]]` as the type of this attribute,
    # which is both an `invalid-mutable-override`, and disallows the assignment below.
    # Now we "fill in" the bare `ClassVar` with the inherited type instead of the inferred
    # type, leaving the declared type of the attribute as `ClassVar[tuple[str, ...]]`. 
    group_by: ClassVar = ("record",)

Record.group_by = ("record", "source")  # previously an error, now OK
```

Ty now checks the default against `Message.group_by`. Inside the
`Record` class body, ty knows that `group_by` is `("record",)`. Later
assignments through `Record` can use any tuple of strings.

A subclass can still redeclare the type explicitly with `ClassVar[...]`.

Follow-up to #30293.

## Ecosystem report

At
[`20ae89c`](https://github.com/astral-sh/ruff/commit/20ae89ca2d779e5cdbdb13d48cc0e26998731662),
ty removes 39 false-positive override errors across five projects. All
39 came from subclasses repeating a bare `ClassVar` with a valid
default. The public run found no new stable diagnostics or project
failures. [Full report and
examples](https://github.com/astral-sh/ruff/pull/30396#issuecomment-6841752291)
2026-11-12 18:39:17 -08:00
..
2026-11-12 17:26:23 +00:00
2026-11-10 10:02:17 -05:00
2026-11-12 17:26:23 +00:00
2026-11-12 17:26:23 +00:00