@@ -80,13 +80,14 @@ def _inflate_struct_attributes(
8080 current_offset = 0
8181 bf_tracker = BitfieldTracker ()
8282 aligned = getattr (reference_type , "_aligned_" , False )
83+ self ._member_offsets = {}
8384
8485 for name , annotation , reference in iterate_annotation_chain (reference_type , terminate_at = struct ):
8586 if name == "_aligned_" :
8687 continue
8788
88- resolved_type , bitfield_field , explicit_offset = self ._resolve_field (
89- name , annotation , reference , inflater , reference_type ,
89+ resolved_type , bitfield_field , explicit_offset = struct_impl ._resolve_field (
90+ name , annotation , reference , inflater , owner = ( self , reference_type . _type_impl ) ,
9091 )
9192
9293 if explicit_offset is not None :
@@ -95,6 +96,10 @@ def _inflate_struct_attributes(
9596 current_offset = explicit_offset
9697
9798 if bitfield_field :
99+ if aligned and bf_tracker .needs_new_group (bitfield_field ):
100+ current_offset += bf_tracker .flush ()
101+ current_offset = _align_offset (current_offset , alignment_of (bitfield_field .backing_type ))
102+ self ._member_offsets [name ] = current_offset
98103 result , offset_delta = bf_tracker .create_bitfield (
99104 bitfield_field , inflater , resolver , current_offset ,
100105 )
@@ -103,20 +108,21 @@ def _inflate_struct_attributes(
103108 current_offset += bf_tracker .flush ()
104109 if aligned and explicit_offset is None :
105110 current_offset = _align_offset (current_offset , alignment_of (resolved_type ))
111+ self ._member_offsets [name ] = current_offset
106112 result = resolved_type (resolver .relative_from_own (current_offset , 0 ))
107113 current_offset += size_of (result )
108114
109115 self ._members [name ] = result
110116
111117 current_offset += bf_tracker .flush ()
112118
119+ @staticmethod
113120 def _resolve_field (
114- self : struct_impl ,
115121 name : str ,
116122 annotation : type ,
117123 reference : type ,
118124 inflater : TypeRegistry ,
119- reference_type : type ,
125+ owner : tuple [ obj , type ] | None ,
120126 ) -> tuple [object | None , BitfieldField | None , int | None ]:
121127 """Resolve a single struct field annotation to its inflater or BitfieldField.
122128
@@ -126,7 +132,7 @@ def _resolve_field(
126132 explicit_offset is set when an OffsetAttribute is present.
127133 """
128134 if name not in reference .__dict__ :
129- return inflater .inflater_for (annotation , owner = ( self , reference_type . _type_impl ) ), None , None
135+ return inflater .inflater_for (annotation , owner = owner ), None , None
130136
131137 attrs = getattr (reference , name )
132138 if not isinstance (attrs , tuple ):
@@ -144,15 +150,15 @@ def _resolve_field(
144150 bitfield_field = attr
145151 elif isinstance (attr , Field ):
146152 resolved_type = inflater .inflater_for (
147- (attr , annotation ), owner = ( self , reference_type . _type_impl ) ,
153+ (attr , annotation ), owner = owner ,
148154 )
149155 elif isinstance (attr , OffsetAttribute ):
150156 explicit_offset = attr .offset
151157 else :
152158 raise TypeError ("Only Field, BitfieldField, and OffsetAttribute are allowed in attributes." )
153159
154160 if not resolved_type and not bitfield_field :
155- resolved_type = inflater .inflater_for (annotation , owner = ( self , reference_type . _type_impl ) )
161+ resolved_type = inflater .inflater_for (annotation , owner = owner )
156162
157163 return resolved_type , bitfield_field , explicit_offset
158164
@@ -168,46 +174,39 @@ def compute_own_size(cls: type[struct_impl], reference_type: type) -> None:
168174 if name == "_aligned_" :
169175 continue
170176
171- bitfield_field = None
172- attribute = None
173- has_explicit_offset = False
174-
175- if name in reference .__dict__ :
176- attrs = getattr (reference , name )
177- if not isinstance (attrs , tuple ):
178- attrs = (attrs ,)
179-
180- if sum (isinstance (attr , Field ) for attr in attrs ) > 1 :
181- raise ValueError ("Only one Field is allowed per attribute." )
182-
183- for attr in attrs :
184- if isinstance (attr , BitfieldField ):
185- bitfield_field = attr
186- elif isinstance (attr , Field ):
187- attribute = cls ._inflater .inflater_for ((attr , annotation ), (None , cls ))(None )
188- elif isinstance (attr , OffsetAttribute ):
189- has_explicit_offset = True
190- offset = attr .offset
191- if offset < size :
192- raise ValueError ("Offset must be greater than the current size." )
193- size = offset
194-
195- if not attribute and not bitfield_field :
196- attribute = cls ._inflater .inflater_for (annotation , (None , cls ))
197- elif isinstance (annotation , Field ):
198- attribute = cls ._inflater .inflater_for ((annotation , annotation .base_type ), (None , cls ))(None )
199- else :
200- attribute = cls ._inflater .inflater_for (annotation , (None , cls ))
177+ resolved_type , bitfield_field , explicit_offset = struct_impl ._resolve_field (
178+ name , annotation , reference , cls ._inflater , owner = (None , cls ),
179+ )
180+
181+ has_explicit_offset = explicit_offset is not None
182+ if has_explicit_offset :
183+ if explicit_offset < size :
184+ raise ValueError ("Offset must be greater than the current size." )
185+ size = explicit_offset
201186
202187 if bitfield_field :
188+ if aligned and bf_tracker .needs_new_group (bitfield_field ):
189+ size += bf_tracker .flush ()
190+ field_align = alignment_of (bitfield_field .backing_type )
191+ max_alignment = max (max_alignment , field_align )
192+ size = _align_offset (size , field_align )
203193 size += bf_tracker .compute_size (bitfield_field )
204194 else :
205195 size += bf_tracker .flush ()
196+ # Get attribute for size computation — try size_of directly first,
197+ # falling back to calling the inflater with None for complex fields.
198+ # Direct size_of avoids recursion for forward-ref pointers.
199+ try :
200+ attribute_size = size_of (resolved_type )
201+ attribute = resolved_type
202+ except (ValueError , TypeError ):
203+ attribute = resolved_type (None )
204+ attribute_size = size_of (attribute )
206205 if aligned and not has_explicit_offset :
207206 field_align = alignment_of (attribute )
208207 max_alignment = max (max_alignment , field_align )
209208 size = _align_offset (size , field_align )
210- size += size_of ( attribute )
209+ size += attribute_size
211210
212211 size += bf_tracker .flush ()
213212
@@ -232,21 +231,19 @@ def get(self: struct_impl) -> str:
232231 return f"{ name } (address={ addr } , size={ size_of (self )} )"
233232
234233 def to_bytes (self : struct_impl ) -> bytes :
235- """Return the serialized representation of the struct."""
236- return b"" .join (member .to_bytes () for member in self ._members .values ())
234+ """Return the serialized representation of the struct, including padding."""
235+ if self ._frozen :
236+ return self ._frozen_struct_bytes
237+ return self .resolver .resolve (size_of (self ), 0 )
237238
238239 def to_dict (self : struct_impl ) -> dict [str , object ]:
239240 """Return a JSON-serializable dict of field names to values."""
240241 return {name : member .to_dict () for name , member in self ._members .items ()}
241242
242243 def hexdump (self : struct_impl ) -> str :
243244 """Return a hex dump of this struct's bytes with field annotations."""
244- annotations = {}
245- offset = 0
246- for name , member in self ._members .items ():
247- annotations [offset ] = name
248- offset += len (member .to_bytes ())
249-
245+ member_offsets = object .__getattribute__ (self , "_member_offsets" )
246+ annotations = {member_offsets [name ]: name for name in self ._members }
250247 address = struct_impl .address .fget (self ) if not self ._frozen else 0
251248 return format_hexdump (self .to_bytes (), address , annotations )
252249
@@ -255,8 +252,9 @@ def _set(self: struct_impl, _: str) -> None:
255252 raise RuntimeError ("Cannot set the value of a struct." )
256253
257254 def freeze (self : struct_impl ) -> None :
258- """Freeze the struct."""
259- # The struct has no implicit value, but it must freeze its members
255+ """Freeze the struct, capturing the full byte representation including padding."""
256+ self ._frozen_struct_bytes = self .resolver .resolve (size_of (self ), 0 )
257+
260258 for member in self ._members .values ():
261259 member .freeze ()
262260
@@ -288,7 +286,7 @@ def __repr__(self: struct_impl) -> str:
288286 def __eq__ (self : struct_impl , value : object ) -> bool :
289287 """Return whether the struct is equal to the given value."""
290288 if not isinstance (value , struct_impl ):
291- return False
289+ return NotImplemented
292290
293291 if size_of (self ) != size_of (value ):
294292 return False
0 commit comments