Coverage for src/objectstate/reified_generics.py: 75%
126 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 07:22 +0000
1"""
2Reified Generics Prototype - Runtime-preserved type parameters.
4This module provides a proof-of-concept implementation of reified generics,
5where `List[int]` and `List[str]` are distinct types at runtime.
7Core concepts:
8- ReifiedMeta: Metaclass with custom __instancecheck__ and __subclasscheck__
9- Type caching: Same parameterization returns same type object
10- Covariance: issubclass(List[int], List[object]) works correctly
12Usage:
13 from objectstate.reified_generics import List, Dict
15 x = List[int]([1, 2, 3])
16 isinstance(x, List[int]) # True
17 isinstance(x, List[str]) # False
18 type(x).__args__ # (int,)
19"""
21from typing import Dict as TypingDict, Tuple, Any, TypeVar, get_args, get_origin, Optional as TypingOptional
22import weakref
24# =============================================================================
25# TYPE CACHE - immutable reified generic identity
26# =============================================================================
28_reified_cache: TypingDict[Tuple[type, tuple], type] = {}
31def _get_cached_type(origin: type, args: tuple) -> type:
32 """Get or create cached reified type."""
33 key = (origin, args)
34 if key in _reified_cache:
35 return _reified_cache[key]
36 return None
39def _cache_type(origin: type, args: tuple, reified_type: type) -> None:
40 """Cache a reified type."""
41 _reified_cache[(origin, args)] = reified_type
44# =============================================================================
45# REIFIED METACLASS - Pattern from GlobalConfigMeta.__instancecheck__
46# =============================================================================
48class ReifiedMeta(type):
49 """
50 Metaclass for reified generic types.
52 Provides custom isinstance/issubclass behavior that respects type parameters.
53 """
55 def __instancecheck__(cls, instance: Any) -> bool:
56 """Check if instance is of this reified type."""
57 if not hasattr(cls, '__args__') or not hasattr(cls, '__origin__'):
58 return super().__instancecheck__(instance)
60 inst_type = type(instance)
62 # Fast path: exact type match (reified instance)
63 if inst_type is cls:
64 return True
66 # Check if instance has reified type info
67 if hasattr(inst_type, '__args__') and hasattr(inst_type, '__origin__'):
68 # Same origin and args = same type
69 if inst_type.__origin__ is cls.__origin__:
70 return inst_type.__args__ == cls.__args__
72 # Fallback: check origin only (plain list passed to List[int])
73 # This is the "trust mode" - we check the container type, not elements
74 if isinstance(instance, cls.__origin__):
75 # For non-reified instances, we can optionally check elements
76 # For now, return False to enforce reified construction
77 return False
79 return False
81 def __subclasscheck__(cls, subclass: type) -> bool:
82 """Check subclass with covariance support."""
83 if not hasattr(cls, '__args__') or not hasattr(cls, '__origin__'):
84 return super().__subclasscheck__(subclass)
86 # Handle reified subclass
87 if hasattr(subclass, '__args__') and hasattr(subclass, '__origin__'):
88 # Same origin required
89 if subclass.__origin__ is not cls.__origin__:
90 # Check if subclass origin inherits from cls origin
91 if not issubclass(subclass.__origin__, cls.__origin__):
92 return False
94 # Covariance: subclass args must be subtypes of cls args
95 if len(subclass.__args__) != len(cls.__args__):
96 return False
98 for sub_arg, cls_arg in zip(subclass.__args__, cls.__args__):
99 # Handle non-type args (like Literal values)
100 if not isinstance(sub_arg, type) or not isinstance(cls_arg, type):
101 if sub_arg != cls_arg:
102 return False
103 elif not issubclass(sub_arg, cls_arg):
104 return False
106 return True
108 # Plain type vs reified: list is subclass of List[object] (top type)
109 if issubclass(subclass, cls.__origin__):
110 # Only if cls has object/Any as all args
111 return all(arg is object for arg in cls.__args__)
113 return False
115 def __hash__(cls) -> int:
116 """Reified types are hashable for use as dict keys."""
117 if hasattr(cls, '__origin__') and hasattr(cls, '__args__'):
118 return hash((cls.__origin__, cls.__args__))
119 return super().__hash__()
121 def __eq__(cls, other: Any) -> bool:
122 """Equality based on origin and args."""
123 if not isinstance(other, type):
124 return False
125 if hasattr(cls, '__origin__') and hasattr(other, '__origin__'):
126 if hasattr(cls, '__args__') and hasattr(other, '__args__'):
127 return cls.__origin__ is other.__origin__ and cls.__args__ == other.__args__
128 return cls is other
130 def __repr__(cls) -> str:
131 if hasattr(cls, '__origin__') and hasattr(cls, '__args__'):
132 args_str = ', '.join(
133 arg.__name__ if hasattr(arg, '__name__') else repr(arg)
134 for arg in cls.__args__
135 )
136 return f"{cls.__origin__.__name__}[{args_str}]"
137 return super().__repr__()
140# =============================================================================
141# REIFIED TYPE FACTORY
142# =============================================================================
144def _make_reified_type(origin: type, args: tuple) -> type:
145 """Create a new reified type for origin[args]."""
146 # Check cache first
147 cached = _get_cached_type(origin, args)
148 if cached is not None:
149 return cached
151 # Create type name
152 args_str = ', '.join(
153 arg.__name__ if hasattr(arg, '__name__') else repr(arg)
154 for arg in args
155 )
156 type_name = f'{origin.__name__}[{args_str}]'
158 # Create the reified type using metaclass
159 reified_type = ReifiedMeta(
160 type_name,
161 (origin,),
162 {
163 '__origin__': origin,
164 '__args__': args,
165 '__reified__': True,
166 '__module__': origin.__module__,
167 }
168 )
170 # Cache and return
171 _cache_type(origin, args, reified_type)
172 return reified_type
175# =============================================================================
176# REIFIED WRAPPER CLASSES - Drop-in replacements for stdlib generics
177# =============================================================================
179class ReifiedList(list):
180 """
181 Reified list that preserves type parameters at runtime.
183 Usage:
184 x = List[int]([1, 2, 3])
185 type(x) # List[int]
186 isinstance(x, List[int]) # True
187 """
188 __origin__ = list
189 __args__: tuple = ()
190 __reified__ = True
192 def __class_getitem__(cls, params):
193 """Create or retrieve cached reified type."""
194 if not isinstance(params, tuple):
195 params = (params,)
196 return _make_reified_type(list, params)
198 def __repr__(self):
199 return f"{type(self)!r}({super().__repr__()})"
202class ReifiedDict(dict):
203 """
204 Reified dict that preserves type parameters at runtime.
206 Usage:
207 x = Dict[str, int]({"a": 1})
208 type(x) # Dict[str, int]
209 isinstance(x, Dict[str, int]) # True
210 """
211 __origin__ = dict
212 __args__: tuple = ()
213 __reified__ = True
215 def __class_getitem__(cls, params):
216 """Create or retrieve cached reified type."""
217 if not isinstance(params, tuple):
218 params = (params,)
219 return _make_reified_type(dict, params)
221 def __repr__(self):
222 return f"{type(self)!r}({super().__repr__()})"
225class ReifiedSet(set):
226 """Reified set that preserves type parameters at runtime."""
227 __origin__ = set
228 __args__: tuple = ()
229 __reified__ = True
231 def __class_getitem__(cls, params):
232 if not isinstance(params, tuple):
233 params = (params,)
234 return _make_reified_type(set, params)
237class ReifiedTuple(tuple):
238 """Reified tuple that preserves type parameters at runtime."""
239 __origin__ = tuple
240 __args__: tuple = ()
241 __reified__ = True
243 def __class_getitem__(cls, params):
244 if not isinstance(params, tuple):
245 params = (params,)
246 return _make_reified_type(tuple, params)
249# =============================================================================
250# PUBLIC API - Aliased names matching typing module
251# =============================================================================
253List = ReifiedList
254Dict = ReifiedDict
255Set = ReifiedSet
256Tuple = ReifiedTuple
257Optional = TypingOptional
260# =============================================================================
261# UTILITY FUNCTIONS
262# =============================================================================
264def is_reified(t: type) -> bool:
265 """Check if a type is a reified generic."""
266 return getattr(t, '__reified__', False)
269def get_reified_args(t: type) -> tuple:
270 """Get type arguments from a reified type."""
271 return getattr(t, '__args__', ())
274def get_reified_origin(t: type) -> type:
275 """Get origin type from a reified type."""
276 return getattr(t, '__origin__', t)
279def clear_cache() -> None:
280 """Clear the reified type cache (for testing)."""
281 _reified_cache.clear()
284# =============================================================================
285# DECORATOR FOR CUSTOM CLASSES
286# =============================================================================
288def reified(cls: type) -> type:
289 """
290 Decorator to make a class support reified generics.
292 Usage:
293 @reified
294 class Container:
295 def __init__(self, items):
296 self.items = items
298 x = Container[int]([1, 2, 3])
299 isinstance(x, Container[int]) # True
300 """
301 original_class_getitem = getattr(cls, '__class_getitem__', None)
303 def __class_getitem__(cls_inner, params):
304 if not isinstance(params, tuple):
305 params = (params,)
306 return _make_reified_type(cls, params)
308 cls.__class_getitem__ = classmethod(lambda c, p: __class_getitem__(c, p))
309 cls.__origin__ = cls
310 cls.__reified__ = True
312 return cls