mirror of
https://github.com/astral-sh/ruff.git
synced 2026-11-15 14:28:27 +08:00
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)