Skip to content

Fix: Reject ClassVar overrides of inherited struct fields - #1189

Open
LmeSzinc wants to merge 1 commit into
msgspec:mainfrom
LmeSzinc:reject-classvar-override
Open

LmeSzinc wants to merge 1 commit into
msgspec:mainfrom
LmeSzinc:reject-classvar-override

Conversation

@LmeSzinc

Copy link
Copy Markdown

Fixes #1086 , following #1088 (review)

The implement reuses variable that will be later used, so no performance impact in normal scenario.

Example:

from typing import ClassVar

import msgspec


class Base(msgspec.Struct):
    name: str = "base_name"


class Child(Base):
    name: ClassVar[str] = "overridden"

Output:

$ python example.py
Traceback (most recent call last):
  File "example.py", line 10, in <module>
    class Child(Base):
TypeError: Cannot override inherited field 'name' with a `ClassVar` annotation in struct 'Child'

A `ClassVar` annotation can't remove a field inherited from a struct
base: the inherited field is baked into the slot layout and the field
list, while attribute access resolves to the class variable. The result
was an inconsistent struct -- the field stayed in `__struct_fields__`
and was still encoded, but the stored value was unreachable, and typed
decoders failed with `Type 'typing.ClassVar[...]' is not supported`.

Raise a `TypeError` when the struct is defined instead:

    Cannot override inherited field 'x' with a `ClassVar` annotation in struct 'Child'

Fixes msgspec#1086

@Siyet Siyet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for turning this around so quickly.

Failing at class definition time is the right call. On main the definition is accepted and the result is inconsistent: Child("x").name returns the class variable, the encoded output of that same instance carries the value stored in the slot, and building a decoder raises TypeError: Type 'typing.ClassVar[str]' is not supported. The check is in the right place and the error path is correct.

One request before this lands: the "Class Variables" section of docs/structs.rst still states that struct types exclude any annotation wrapped in typing.ClassVar from their fields, with no exception. A sentence there about the new restriction would help whoever runs into the error.

@provinzkraut, this follows your #1088 review asking for a loud failure at struct definition time. On #1086 you wanted other committers to weigh in first, and nobody has since. Since this turns a definition that is accepted today into a hard error, does landing it this way look right to you?

This branch was successfully deployed

1 active deployment
docs-preview — 38760d8e Deployed Sep 18, 2026 by LmeSzinc via Deploy preview #487
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Struct field overridden as ClassVar in subclass produces unexpected behavior

2 participants