@@ -2554,8 +2554,8 @@ class Example(msgspec.Struct, Generic[T]):
25542554 assert result1 is not result2
25552555
25562556 def test_fields_cached_frozen_struct (self ):
2557- # frozen=True blocks setattr on instances, not on the class itself,
2558- # so the cache slot is still writable via StructMeta's tp_setattro .
2557+ # The cache is a side table keyed on the class, so frozen structs
2558+ # (which block setattr on instances) are cached like any other .
25592559 class Example (msgspec .Struct , frozen = True ):
25602560 x : int
25612561 y : str = "hello"
@@ -2564,6 +2564,29 @@ class Example(msgspec.Struct, frozen=True):
25642564 result2 = msgspec .structs .fields (Example )
25652565 assert result1 is result2
25662566
2567+ def test_fields_cache_does_not_mutate_class (self ):
2568+ class Example (msgspec .Struct ):
2569+ x : int
2570+
2571+ msgspec .structs .fields (Example )
2572+ # The cache lives in a side table, not on the class, so inspecting
2573+ # the struct must not add any attributes to it.
2574+ assert "__struct_field_info__" not in Example .__dict__
2575+ assert not any ("field_info" in name for name in vars (Example ))
2576+
2577+ def test_fields_cache_not_inherited_by_subclass (self ):
2578+ class Base (msgspec .Struct ):
2579+ x : int
2580+
2581+ class Child (Base ):
2582+ y : int
2583+
2584+ base_fields = msgspec .structs .fields (Base )
2585+ child_fields = msgspec .structs .fields (Child )
2586+ assert [f .name for f in base_fields ] == ["x" ]
2587+ assert [f .name for f in child_fields ] == ["x" , "y" ]
2588+ assert base_fields is not child_fields
2589+
25672590
25682591class TestClassVar :
25692592 def case1 (self ):
0 commit comments