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

1""" 

2Reified Generics Prototype - Runtime-preserved type parameters. 

3 

4This module provides a proof-of-concept implementation of reified generics, 

5where `List[int]` and `List[str]` are distinct types at runtime. 

6 

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 

11 

12Usage: 

13 from objectstate.reified_generics import List, Dict 

14  

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""" 

20 

21from typing import Dict as TypingDict, Tuple, Any, TypeVar, get_args, get_origin, Optional as TypingOptional 

22import weakref 

23 

24# ============================================================================= 

25# TYPE CACHE - immutable reified generic identity 

26# ============================================================================= 

27 

28_reified_cache: TypingDict[Tuple[type, tuple], type] = {} 

29 

30 

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 

37 

38 

39def _cache_type(origin: type, args: tuple, reified_type: type) -> None: 

40 """Cache a reified type.""" 

41 _reified_cache[(origin, args)] = reified_type 

42 

43 

44# ============================================================================= 

45# REIFIED METACLASS - Pattern from GlobalConfigMeta.__instancecheck__ 

46# ============================================================================= 

47 

48class ReifiedMeta(type): 

49 """ 

50 Metaclass for reified generic types. 

51  

52 Provides custom isinstance/issubclass behavior that respects type parameters. 

53 """ 

54 

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) 

59 

60 inst_type = type(instance) 

61 

62 # Fast path: exact type match (reified instance) 

63 if inst_type is cls: 

64 return True 

65 

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__ 

71 

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 

78 

79 return False 

80 

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) 

85 

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 

93 

94 # Covariance: subclass args must be subtypes of cls args 

95 if len(subclass.__args__) != len(cls.__args__): 

96 return False 

97 

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 

105 

106 return True 

107 

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__) 

112 

113 return False 

114 

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__() 

120 

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 

129 

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__() 

138 

139 

140# ============================================================================= 

141# REIFIED TYPE FACTORY 

142# ============================================================================= 

143 

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 

150 

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}]' 

157 

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 ) 

169 

170 # Cache and return 

171 _cache_type(origin, args, reified_type) 

172 return reified_type 

173 

174 

175# ============================================================================= 

176# REIFIED WRAPPER CLASSES - Drop-in replacements for stdlib generics 

177# ============================================================================= 

178 

179class ReifiedList(list): 

180 """ 

181 Reified list that preserves type parameters at runtime. 

182 

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 

191 

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) 

197 

198 def __repr__(self): 

199 return f"{type(self)!r}({super().__repr__()})" 

200 

201 

202class ReifiedDict(dict): 

203 """ 

204 Reified dict that preserves type parameters at runtime. 

205 

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 

214 

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) 

220 

221 def __repr__(self): 

222 return f"{type(self)!r}({super().__repr__()})" 

223 

224 

225class ReifiedSet(set): 

226 """Reified set that preserves type parameters at runtime.""" 

227 __origin__ = set 

228 __args__: tuple = () 

229 __reified__ = True 

230 

231 def __class_getitem__(cls, params): 

232 if not isinstance(params, tuple): 

233 params = (params,) 

234 return _make_reified_type(set, params) 

235 

236 

237class ReifiedTuple(tuple): 

238 """Reified tuple that preserves type parameters at runtime.""" 

239 __origin__ = tuple 

240 __args__: tuple = () 

241 __reified__ = True 

242 

243 def __class_getitem__(cls, params): 

244 if not isinstance(params, tuple): 

245 params = (params,) 

246 return _make_reified_type(tuple, params) 

247 

248 

249# ============================================================================= 

250# PUBLIC API - Aliased names matching typing module 

251# ============================================================================= 

252 

253List = ReifiedList 

254Dict = ReifiedDict 

255Set = ReifiedSet 

256Tuple = ReifiedTuple 

257Optional = TypingOptional 

258 

259 

260# ============================================================================= 

261# UTILITY FUNCTIONS 

262# ============================================================================= 

263 

264def is_reified(t: type) -> bool: 

265 """Check if a type is a reified generic.""" 

266 return getattr(t, '__reified__', False) 

267 

268 

269def get_reified_args(t: type) -> tuple: 

270 """Get type arguments from a reified type.""" 

271 return getattr(t, '__args__', ()) 

272 

273 

274def get_reified_origin(t: type) -> type: 

275 """Get origin type from a reified type.""" 

276 return getattr(t, '__origin__', t) 

277 

278 

279def clear_cache() -> None: 

280 """Clear the reified type cache (for testing).""" 

281 _reified_cache.clear() 

282 

283 

284# ============================================================================= 

285# DECORATOR FOR CUSTOM CLASSES 

286# ============================================================================= 

287 

288def reified(cls: type) -> type: 

289 """ 

290 Decorator to make a class support reified generics. 

291 

292 Usage: 

293 @reified 

294 class Container: 

295 def __init__(self, items): 

296 self.items = items 

297 

298 x = Container[int]([1, 2, 3]) 

299 isinstance(x, Container[int]) # True 

300 """ 

301 original_class_getitem = getattr(cls, '__class_getitem__', None) 

302 

303 def __class_getitem__(cls_inner, params): 

304 if not isinstance(params, tuple): 

305 params = (params,) 

306 return _make_reified_type(cls, params) 

307 

308 cls.__class_getitem__ = classmethod(lambda c, p: __class_getitem__(c, p)) 

309 cls.__origin__ = cls 

310 cls.__reified__ = True 

311 

312 return cls