[ty] Infer nested classes as read-only attributes (#29346)
ty now accepts `Child.Meta` as an override when it inherits from
`Base.Meta`. Previously, ty rejected the override because the two class
objects are distinct.
```python
class Base:
class Meta: ...
class Child(Base):
class Meta(Base.Meta): ... # OK
class Other(Base):
class Meta: ... # Error: unrelated to Base.Meta
```
`Base.Meta` can still safely reveal the singleton class type of `Meta`,
since the expression `Base` has a singleton class type that doesn't
include subclasses. But a caller reading `obj.Meta` could get
`Child.Meta`, so ty allows for subclasses in the inferred type.
```python
reveal_type(Base.Meta) # <class 'Meta'>
def inspect(obj: Base, cls: type[Base]) -> None:
reveal_type(obj.Meta) # type[Meta]
reveal_type(cls.Meta) # type[Meta]
```
The nested class declaration is read-only. If `obj` is a `Child`,
assigning `Base.Meta` would replace its more specific `Child.Meta`;
making `Base.Meta` read-only allows `Child` to safely narrow its type.
```python
def update(obj: Base) -> None:
obj.Meta = Base.Meta # Error
del obj.Meta # Error
```
A subclass can explicitly declare a writable instance attribute when its
instances need to replace `Meta`.
```python
class Configurable(Base):
def __init__(self) -> None:
self.Meta: type[Base.Meta] = Base.Meta
def configure(obj: Configurable) -> None:
obj.Meta = Child.Meta # OK
```
The same read-only rule applies to nested classes declared on protocols
and metaclasses.
### Django
We have a special-case for Django in main allowing inner `Meta` class
overrides on Django models. This PR doesn't remove that special case.
Two abstract Django models can each declare an unrelated `Meta`. A model
inheriting from both uses the first base’s `Meta` options by default.
Ordinary override checking would report a conflict between those
classes, so ty keeps the Django exception for this case. [Django’s
`Meta` inheritance
rules](https://docs.djangoproject.com/en/5.2/topics/db/models/#meta-inheritance)
Closes https://github.com/astral-sh/ty/issues/4711.
## Test plan
Compared Rust type layouts on aarch64 with debug assertions enabled and
disabled. Widening `TypeQualifiers` to `u16` fit in existing padding in
`TypeAndQualifiers`, `PlaceAndQualifiers`, and `ProtocolMemberData`. All
three kept their original size.
## Ecosystem report
The public comparison found 168 stable diagnostic changes on
`c2d14afc02318e6fdf041a6983447aa7fb6008a6`. All follow from the new
nested-class behavior. [Full ecosystem
report](https://github.com/astral-sh/ruff/pull/29346#issuecomment-6172170919)
- Spark gained two default warnings and Alerta gained eight opt-in
warnings. Both use a nested class accessed through an instance as a
base, so ty can no longer determine the exact method resolution order.
- Three diagnostics disappeared in Spack and Freqtrade.
- 155 existing override errors now display the inferred class-object
types.