@@ -56,9 +56,11 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
5656 self .endian = endian
5757
5858 self .consts = {}
59+ self .types = {}
5960 self .includes = []
61+
6062 # fmt: off
61- self . typedefs = {
63+ initial_types = {
6264 # Internal types
6365 "int8" : self ._make_packed_type ("int8" , "b" , int ),
6466 "uint8" : self ._make_packed_type ("uint8" , "B" , int ),
@@ -102,6 +104,21 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
102104 "signed long long" : "int64" ,
103105 "unsigned long long" : "uint64" ,
104106
107+ # Other convenience types
108+ "u1" : "uint8" ,
109+ "u2" : "uint16" ,
110+ "u4" : "uint32" ,
111+ "u8" : "uint64" ,
112+ "u16" : "uint128" ,
113+ "__u8" : "uint8" ,
114+ "__u16" : "uint16" ,
115+ "__u32" : "uint32" ,
116+ "__u64" : "uint64" ,
117+ "uchar" : "uint8" ,
118+ "ushort" : "uint16" ,
119+ "uint" : "uint32" ,
120+ "ulong" : "uint32" ,
121+
105122 # Windows types
106123 "BYTE" : "uint8" ,
107124 "CHAR" : "char" ,
@@ -169,24 +186,12 @@ def __init__(self, load: str = "", *, endian: str = "<", pointer: str | None = N
169186 "_DWORD" : "uint32" ,
170187 "_QWORD" : "uint64" ,
171188 "_OWORD" : "uint128" ,
172-
173- # Other convenience types
174- "u1" : "uint8" ,
175- "u2" : "uint16" ,
176- "u4" : "uint32" ,
177- "u8" : "uint64" ,
178- "u16" : "uint128" ,
179- "__u8" : "uint8" ,
180- "__u16" : "uint16" ,
181- "__u32" : "uint32" ,
182- "__u64" : "uint64" ,
183- "uchar" : "uint8" ,
184- "ushort" : "uint16" ,
185- "uint" : "uint32" ,
186- "ulong" : "uint32" ,
187189 }
188190 # fmt: on
189191
192+ for name , type_ in initial_types .items ():
193+ self .add_type (name , type_ )
194+
190195 pointer = pointer or ("uint64" if sys .maxsize > 2 ** 32 else "uint32" )
191196 self .pointer : type [BaseType ] = self .resolve (pointer )
192197 self ._anonymous_count = 0
@@ -201,27 +206,29 @@ def __getattr__(self, attr: str) -> Any:
201206 pass
202207
203208 try :
204- return self .resolve ( self . typedefs [attr ])
209+ return self .types [attr ]
205210 except KeyError :
206211 pass
207212
208- raise AttributeError (f"Invalid attribute: { attr } " )
213+ raise AttributeError (f"' { type ( self ). __name__ } ' object has no attribute { attr !r } " )
209214
210215 def __copy__ (self ) -> cstruct :
211216 cs = cstruct (endian = self .endian , pointer = self .pointer .__name__ )
212217 cs ._anonymous_count = self ._anonymous_count
213- cs .consts = self .consts .copy ()
214218 cs .includes = self .includes .copy ()
215219
216- # Update typedefs to point to the new cstruct instance
217- for name , type in self .typedefs .items ():
218- if name in cs .typedefs :
220+ # Update types to point to the new cstruct instance
221+ for name , type_ in self .types .items ():
222+ if name in cs .types :
219223 continue
220224
221- if isinstance (type , MetaType ):
222- new_type = copy .copy (type )
225+ if isinstance (type_ , MetaType ):
226+ new_type = copy .copy (type_ )
223227 new_type .cs = cs
224- cs .typedefs [name ] = new_type
228+ cs .add_type (name , new_type )
229+
230+ for name , value in self .consts .items ():
231+ cs .add_const (name , value )
225232
226233 return cs
227234
@@ -230,22 +237,34 @@ def _next_anonymous(self) -> str:
230237 self ._anonymous_count += 1
231238 return name
232239
240+ def _add_attr (self , name : str , value : Any ) -> None :
241+ # Names that collide with the cstruct class (e.g. a struct named ``load``) are not set as attributes
242+ # to avoid breaking the instance. They remain accessible through ``resolve`` and the type dicts.
243+ if name in _RESERVED_NAMES :
244+ return
245+ setattr (self , name , value )
246+
233247 def add_type (self , name : str , type_ : type [BaseType ] | str , replace : bool = False ) -> None :
234- """Add a type or type reference .
248+ """Add a type or type alias .
235249
236250 Only use this method when creating type aliases or adding already bound types.
251+ All types will be resolved to their actual type objects prior to being added.
237252
238253 Args:
239254 name: Name of the type to be added.
240255 type_: The type to be added. Can be a str reference to another type or a compatible type class.
256+ If a str is given, it will be resolved to the actual type object.
257+ replace: Whether to replace the type if it already exists.
241258
242259 Raises:
243260 ValueError: If the type already exists.
244261 """
245- if not replace and (name in self .typedefs and self .resolve (self .typedefs [name ]) != self .resolve (type_ )):
262+ typeobj = self .resolve (type_ )
263+ if not replace and (existing := self .types .get (name )) is not None and existing is not typeobj :
246264 raise ValueError (f"Duplicate type: { name } " )
247265
248- self .typedefs [name ] = type_
266+ self .types [name ] = typeobj
267+ self ._add_attr (name , typeobj )
249268
250269 addtype = add_type
251270
@@ -266,6 +285,28 @@ def add_custom_type(
266285 """
267286 self .add_type (name , self ._make_type (name , (type_ ,), size , alignment = alignment , attrs = kwargs ))
268287
288+ def add_const (self , name : str , value : Any ) -> None :
289+ """Add a constant value.
290+
291+ Args:
292+ name: Name of the constant to be added.
293+ value: The value of the constant.
294+ """
295+ self .consts [name ] = value
296+ self ._add_attr (name , value )
297+
298+ def del_const (self , name : str ) -> None :
299+ """Delete a constant value.
300+
301+ Args:
302+ name: Name of the constant to be deleted.
303+
304+ Raises:
305+ KeyError: If the constant does not exist.
306+ """
307+ del self .consts [name ]
308+ self .__dict__ .pop (name , None )
309+
269310 def load (self , definition : str , deftype : int | None = None , ** kwargs ) -> cstruct :
270311 """Parse structures from the given definitions using the given definition type.
271312
@@ -328,20 +369,13 @@ def resolve(self, name: type[BaseType] | str) -> type[BaseType]:
328369 Raises:
329370 ResolveError: If the type can't be resolved.
330371 """
331- type_name = name
332- if not isinstance (type_name , str ):
333- return type_name
334-
335- for _ in range (10 ):
336- if type_name not in self .typedefs :
337- raise ResolveError (f"Unknown type { name } " )
372+ if not isinstance (name , str ):
373+ return name
338374
339- type_name = self .typedefs [type_name ]
340-
341- if not isinstance (type_name , str ):
342- return type_name
343-
344- raise ResolveError (f"Recursion limit exceeded while resolving type { name } " )
375+ try :
376+ return self .types [name ]
377+ except KeyError :
378+ raise ResolveError (f"Unknown type { name } " ) from None
345379
346380 def copy (self ) -> cstruct :
347381 """Create a copy of this cstruct instance.
@@ -598,6 +632,18 @@ class void(Void): ...
598632 ulong : TypeAlias = uint32
599633
600634
635+ # Attribute names that types and constants may never shadow: the public API and methods of the cstruct
636+ # class itself, plus the instance attributes created in __init__.
637+ _RESERVED_NAMES = frozenset (dir (cstruct )) | {
638+ "endian" ,
639+ "consts" ,
640+ "types" ,
641+ "includes" ,
642+ "pointer" ,
643+ "_anonymous_count" ,
644+ }
645+
646+
601647def ctypes (structure : type [Structure ]) -> type [_ctypes .Structure ]:
602648 """Create ctypes structures from cstruct structures."""
603649 fields = []
0 commit comments