tpy-lang 0.3.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
  2. tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
  3. tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
  4. tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
  5. tpyc/__init__.py +104 -0
  6. tpyc/__main__.py +6 -0
  7. tpyc/_buildinfo.py +1 -0
  8. tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
  9. tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
  10. tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
  11. tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
  12. tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
  13. tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
  14. tpyc/_data/lib/tpy/_functools_macros.py +80 -0
  15. tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
  16. tpyc/_data/lib/tpy/argparse.py +2062 -0
  17. tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
  18. tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
  19. tpyc/_data/lib/tpy/base64.py +410 -0
  20. tpyc/_data/lib/tpy/bisect.py +39 -0
  21. tpyc/_data/lib/tpy/builtins.py +38 -0
  22. tpyc/_data/lib/tpy/dataclasses.py +354 -0
  23. tpyc/_data/lib/tpy/enum.py +23 -0
  24. tpyc/_data/lib/tpy/functools.py +33 -0
  25. tpyc/_data/lib/tpy/hashlib.py +206 -0
  26. tpyc/_data/lib/tpy/heapq.py +118 -0
  27. tpyc/_data/lib/tpy/io.py +395 -0
  28. tpyc/_data/lib/tpy/json.py +221 -0
  29. tpyc/_data/lib/tpy/math.py +406 -0
  30. tpyc/_data/lib/tpy/random.py +597 -0
  31. tpyc/_data/lib/tpy/re.py +467 -0
  32. tpyc/_data/lib/tpy/socket.py +379 -0
  33. tpyc/_data/lib/tpy/struct.py +178 -0
  34. tpyc/_data/lib/tpy/sys.py +40 -0
  35. tpyc/_data/lib/tpy/time.py +39 -0
  36. tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
  37. tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
  38. tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
  39. tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
  40. tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
  41. tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
  42. tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
  43. tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
  44. tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
  45. tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
  46. tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
  47. tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
  48. tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
  49. tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
  50. tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
  51. tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
  52. tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
  53. tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
  54. tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
  55. tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
  56. tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
  57. tpyc/_data/lib/tpy/tpy/_version.py +29 -0
  58. tpyc/_data/lib/tpy/tpy/bits.py +28 -0
  59. tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
  60. tpyc/_data/lib/tpy/tpy/extern.py +8 -0
  61. tpyc/_data/lib/tpy/tpy/mem.py +49 -0
  62. tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
  63. tpyc/_data/lib/tpy/tpy/version.py +21 -0
  64. tpyc/_data/lib/tpy/typing.py +13 -0
  65. tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
  66. tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
  67. tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
  68. tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
  69. tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
  70. tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
  71. tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
  72. tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
  73. tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
  74. tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
  75. tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
  76. tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
  77. tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
  78. tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
  79. tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
  80. tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
  81. tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
  82. tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
  83. tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
  84. tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
  85. tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
  86. tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
  87. tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
  88. tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
  89. tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
  90. tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
  91. tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
  92. tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
  93. tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
  94. tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
  95. tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
  96. tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
  97. tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
  98. tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
  99. tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
  100. tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
  101. tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
  102. tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
  103. tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
  104. tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
  105. tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
  106. tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
  107. tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
  108. tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
  109. tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
  110. tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
  111. tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
  112. tpyc/_data/runtime/cpp/third_party/README.md +58 -0
  113. tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
  114. tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
  115. tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
  116. tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
  117. tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
  118. tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
  119. tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
  120. tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
  121. tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
  122. tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
  123. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
  124. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
  125. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
  126. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
  127. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
  128. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
  129. tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
  130. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
  131. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
  132. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
  133. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
  134. tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
  135. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
  136. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
  137. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
  138. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
  139. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
  140. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
  141. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
  142. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
  143. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
  144. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
  145. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
  146. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
  147. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
  148. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
  149. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
  150. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
  151. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
  152. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
  153. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
  154. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
  155. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
  156. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
  157. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
  158. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
  159. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
  160. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
  161. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
  162. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
  163. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
  164. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
  165. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
  166. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
  167. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
  168. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
  169. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
  170. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
  171. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
  172. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
  173. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
  174. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
  175. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
  176. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
  177. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
  178. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
  179. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
  180. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
  181. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
  182. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
  183. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
  184. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
  185. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
  186. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
  187. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
  188. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
  189. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
  190. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
  191. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
  192. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
  193. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
  194. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
  195. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
  196. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
  197. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
  198. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
  199. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
  200. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
  201. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
  202. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
  203. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
  204. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
  205. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
  206. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
  207. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
  208. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
  209. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
  210. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
  211. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
  212. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
  213. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
  214. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
  215. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
  216. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
  217. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
  218. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
  219. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
  220. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
  221. tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
  222. tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
  223. tpyc/build/__init__.py +7 -0
  224. tpyc/build/pcre2.py +122 -0
  225. tpyc/build/third_party.py +413 -0
  226. tpyc/cli.py +822 -0
  227. tpyc/codegen_cpp/__init__.py +18 -0
  228. tpyc/codegen_cpp/builtins.py +484 -0
  229. tpyc/codegen_cpp/context.py +2064 -0
  230. tpyc/codegen_cpp/expressions.py +5940 -0
  231. tpyc/codegen_cpp/functions.py +1913 -0
  232. tpyc/codegen_cpp/gen_async.py +3258 -0
  233. tpyc/codegen_cpp/gen_generators.py +657 -0
  234. tpyc/codegen_cpp/generator.py +2258 -0
  235. tpyc/codegen_cpp/match.py +1997 -0
  236. tpyc/codegen_cpp/param_const.py +172 -0
  237. tpyc/codegen_cpp/protocols.py +907 -0
  238. tpyc/codegen_cpp/records.py +1654 -0
  239. tpyc/codegen_cpp/resumable_cfg.py +1651 -0
  240. tpyc/codegen_cpp/statements.py +4963 -0
  241. tpyc/codegen_cpp/string_dispatch.py +76 -0
  242. tpyc/codegen_cpp/test_context.py +46 -0
  243. tpyc/codegen_cpp/test_param_const.py +113 -0
  244. tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
  245. tpyc/codegen_cpp/type_resolution.py +53 -0
  246. tpyc/codegen_cpp/types.py +436 -0
  247. tpyc/codegen_cpp/variant_access.py +135 -0
  248. tpyc/coercions.py +749 -0
  249. tpyc/compilation_context.py +57 -0
  250. tpyc/compiler.py +3945 -0
  251. tpyc/cycle_detection.py +358 -0
  252. tpyc/diagnostics.py +135 -0
  253. tpyc/dump_types.py +353 -0
  254. tpyc/frontend_diagnostics.py +47 -0
  255. tpyc/frontend_ir/__init__.py +140 -0
  256. tpyc/frontend_ir/lower.py +1098 -0
  257. tpyc/frontend_ir/nodes.py +718 -0
  258. tpyc/frontend_ir/resolver_adapter.py +151 -0
  259. tpyc/frontend_plugin.py +209 -0
  260. tpyc/install_docs.py +81 -0
  261. tpyc/liveness.py +756 -0
  262. tpyc/macro_api.py +1724 -0
  263. tpyc/macro_loader.py +497 -0
  264. tpyc/module_names.py +64 -0
  265. tpyc/modules/__init__.py +31 -0
  266. tpyc/modules/defs.py +89 -0
  267. tpyc/modules/registry.py +36 -0
  268. tpyc/modules/resolver.py +192 -0
  269. tpyc/modules/type_resolution.py +629 -0
  270. tpyc/namespace.py +172 -0
  271. tpyc/parse/__init__.py +84 -0
  272. tpyc/parse/imports.py +490 -0
  273. tpyc/parse/nodes.py +1732 -0
  274. tpyc/parse/parser.py +4043 -0
  275. tpyc/parse/resolve_refs.py +466 -0
  276. tpyc/parse/type_resolver.py +1060 -0
  277. tpyc/prescan.py +254 -0
  278. tpyc/qnames.py +149 -0
  279. tpyc/repl.py +529 -0
  280. tpyc/repl_backends.py +848 -0
  281. tpyc/sema/__init__.py +21 -0
  282. tpyc/sema/analyzer.py +3625 -0
  283. tpyc/sema/bound_check.py +72 -0
  284. tpyc/sema/builder_trace.py +684 -0
  285. tpyc/sema/calls.py +5406 -0
  286. tpyc/sema/compatibility.py +2107 -0
  287. tpyc/sema/context.py +1243 -0
  288. tpyc/sema/expressions.py +3737 -0
  289. tpyc/sema/flow_facts.py +199 -0
  290. tpyc/sema/init_tracker.py +150 -0
  291. tpyc/sema/list_literals.py +69 -0
  292. tpyc/sema/literal_utils.py +27 -0
  293. tpyc/sema/local_deduction.py +1088 -0
  294. tpyc/sema/macros.py +179 -0
  295. tpyc/sema/match.py +1177 -0
  296. tpyc/sema/method_expansion.py +347 -0
  297. tpyc/sema/methods.py +2197 -0
  298. tpyc/sema/mutation_propagation.py +268 -0
  299. tpyc/sema/narrowing.py +857 -0
  300. tpyc/sema/numeric_lattice.py +160 -0
  301. tpyc/sema/operators.py +402 -0
  302. tpyc/sema/overloads.py +841 -0
  303. tpyc/sema/protocols.py +1209 -0
  304. tpyc/sema/reach_analysis.py +202 -0
  305. tpyc/sema/registration.py +3156 -0
  306. tpyc/sema/scope_tracker.py +193 -0
  307. tpyc/sema/statements.py +4426 -0
  308. tpyc/sema/type_ops.py +1879 -0
  309. tpyc/sema/value_range.py +181 -0
  310. tpyc/symbol_binding.py +259 -0
  311. tpyc/test_c3_mro.py +208 -0
  312. tpyc/test_cli_argv.py +52 -0
  313. tpyc/test_compiler.py +559 -0
  314. tpyc/test_contains_type_param.py +101 -0
  315. tpyc/test_cycle_detection.py +221 -0
  316. tpyc/test_dump_types.py +225 -0
  317. tpyc/test_install_docs.py +65 -0
  318. tpyc/test_local_cpp_form.py +135 -0
  319. tpyc/test_macro_loader.py +76 -0
  320. tpyc/test_method_expansion.py +254 -0
  321. tpyc/test_nominal_identity.py +182 -0
  322. tpyc/test_overloads.py +410 -0
  323. tpyc/test_parse.py +303 -0
  324. tpyc/test_parse_type_ref.py +506 -0
  325. tpyc/test_parse_version_info.py +58 -0
  326. tpyc/test_reach_analysis.py +72 -0
  327. tpyc/test_ref_type.py +216 -0
  328. tpyc/test_send_sync_substitution.py +276 -0
  329. tpyc/test_tuple_mutation_propagation.py +206 -0
  330. tpyc/test_type_def_registry.py +1729 -0
  331. tpyc/test_union_types.py +195 -0
  332. tpyc/type_def_registry.py +975 -0
  333. tpyc/typesys.py +5104 -0
@@ -0,0 +1,1729 @@
1
+ """
2
+ Conformance tests for the TypeDef registry.
3
+
4
+ Two layers:
5
+
6
+ 1. Per-qname conformance (Phase A gate): for each qname in the registry,
7
+ assert TypeDef fields agree with what the corresponding TpyType
8
+ instance returns. Catches "registry forgot about X" before the
9
+ subclass is deleted. Once the subclass is gone, the instance and
10
+ registry read from the same source and the check is tautological --
11
+ but it costs nothing to keep.
12
+
13
+ 2. Primitive hard-coded snapshot (Phase D step 0): an explicit golden
14
+ table of every primitive's intrinsic per-qname behavior, compared
15
+ against the live instance. This is *independent* validation --
16
+ hard-coded answers that remain valid after the primitive subclass is
17
+ deleted and the instance starts reading from the registry. The
18
+ snapshot pins what Phase D must preserve.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import pytest
24
+
25
+ from tpyc import typesys as ts
26
+ from tpyc.type_def_registry import (
27
+ TypeCategory,
28
+ get_type_def,
29
+ type_def_of,
30
+ resolve_send_sync,
31
+ _type_defs,
32
+ )
33
+ from tpyc.type_def_registry import IntTraits, FloatTraits
34
+ from tpyc.typesys import ALL_FIXED_INTS
35
+ from tpyc.parse.parser import _FIXED_INT_NAMES as PARSER_FIXED_INT_NAMES
36
+ from tpyc.codegen_cpp.statements import StatementGenerator
37
+ from tpyc.macro_api import _FIXED_INT_NAMES as MACRO_FIXED_INT_NAMES
38
+ from tpyc.compilation_context import activate_compiler
39
+
40
+
41
+ # Map qname -> a concrete TpyType instance with canonical args.
42
+ # Primitives resolve to the typesys singleton. Containers get Int32 /
43
+ # (str, Int32) / (Int32, 10) as their type args.
44
+ def _canonical_instances() -> dict[str, ts.TpyType]:
45
+ I32 = ts.INT32
46
+ STR = ts.STR
47
+ cases: dict[str, ts.TpyType] = {
48
+ "tpy.Int8": ts.INT8, "tpy.Int16": ts.INT16,
49
+ "tpy.Int32": ts.INT32, "tpy.Int64": ts.INT64,
50
+ "tpy.UInt8": ts.UINT8, "tpy.UInt16": ts.UINT16,
51
+ "tpy.UInt32": ts.UINT32, "tpy.UInt64": ts.UINT64,
52
+ "builtins.int": ts.BIGINT,
53
+ "builtins.float": ts.FLOAT,
54
+ "tpy.Float32": ts.FLOAT32,
55
+ "builtins.bool": ts.BOOL,
56
+ "tpy.Char": ts.CHAR,
57
+ "builtins.str": ts.STR,
58
+ "tpy.String": ts.STRING,
59
+ "tpy.StrView": ts.STRVIEW,
60
+ "tpy.FStr": ts.FSTR,
61
+ "builtins.bytes": ts.BYTES,
62
+ "builtins.bytearray": ts.BYTEARRAY,
63
+ "tpy.BytesView": ts.BYTESVIEW,
64
+ "tpy.basic_slice": ts.BASIC_SLICE,
65
+ "builtins.slice": ts.SLICE,
66
+ "builtins.list": ts.make_list(I32),
67
+ "builtins.dict": ts.make_dict(STR, I32),
68
+ "builtins.set": ts.make_set(I32),
69
+ "builtins.dict_keys": ts.make_dict_keys_view(STR, I32),
70
+ "builtins.dict_values": ts.make_dict_values_view(STR, I32),
71
+ "builtins.dict_items": ts.make_dict_items_view(STR, I32),
72
+ "builtins.Range": ts.make_range(I32),
73
+ "tpy.Array": ts.make_array(I32, 10),
74
+ "tpy.Span": ts.make_span(I32),
75
+ "tpy.SpanIter": ts.make_span_iter(I32),
76
+ "tpy.CopyIter": ts.make_copy_iter(I32),
77
+ "tpy.OwnIter": ts.make_own_iter(I32),
78
+ "tpy.Ptr": ts.PtrType(I32),
79
+ "tpy.coro.Waker": ts.WAKER,
80
+ }
81
+ return cases
82
+
83
+
84
+ @pytest.fixture(scope="module")
85
+ def instances() -> dict[str, ts.TpyType]:
86
+ return _canonical_instances()
87
+
88
+
89
+ def test_every_registered_qname_has_a_canonical_instance(instances):
90
+ """Every qname in the registry must have a canonical instance in the
91
+ test so the conformance assertions actually cover it."""
92
+ missing = set(_type_defs) - set(instances)
93
+ assert not missing, f"Registry qnames without canonical instance: {missing}"
94
+
95
+
96
+ def test_type_def_of_matches_registry(instances):
97
+ for qname, inst in instances.items():
98
+ td = type_def_of(inst)
99
+ assert td is not None, f"type_def_of returned None for {qname}"
100
+ assert td.qname == qname, (
101
+ f"qname mismatch: instance.qualified_name()={inst.qualified_name()!r} "
102
+ f"lookup returned TypeDef({td.qname!r})"
103
+ )
104
+
105
+
106
+ def test_is_value_type_matches_subclass(instances):
107
+ for qname, inst in instances.items():
108
+ td = get_type_def(qname)
109
+ assert td is not None
110
+ assert td.is_value_type == inst.is_value_type(), (
111
+ f"{qname}: TypeDef.is_value_type={td.is_value_type} "
112
+ f"but instance.is_value_type()={inst.is_value_type()}"
113
+ )
114
+
115
+
116
+ def test_subscript_borrows_matches_subclass(instances):
117
+ for qname, inst in instances.items():
118
+ td = get_type_def(qname)
119
+ assert td is not None
120
+ assert td.subscript_borrows == inst.subscript_borrows(), (
121
+ f"{qname}: TypeDef.subscript_borrows={td.subscript_borrows} "
122
+ f"but instance.subscript_borrows()={inst.subscript_borrows()}"
123
+ )
124
+
125
+
126
+ def test_is_send_override_matches_subclass(instances):
127
+ for qname, inst in instances.items():
128
+ td = get_type_def(qname)
129
+ assert td is not None
130
+ if td.is_send is not None:
131
+ resolved = resolve_send_sync(td.is_send, inst.type_args)
132
+ assert resolved == inst.is_send(), (
133
+ f"{qname}: TypeDef.is_send resolved={resolved} but "
134
+ f"instance.is_send()={inst.is_send()}"
135
+ )
136
+
137
+
138
+ def test_is_sync_override_matches_subclass(instances):
139
+ for qname, inst in instances.items():
140
+ td = get_type_def(qname)
141
+ assert td is not None
142
+ if td.is_sync is not None:
143
+ resolved = resolve_send_sync(td.is_sync, inst.type_args)
144
+ assert resolved == inst.is_sync(), (
145
+ f"{qname}: TypeDef.is_sync resolved={resolved} but "
146
+ f"instance.is_sync()={inst.is_sync()}"
147
+ )
148
+
149
+
150
+ def test_cpp_formatter_matches_subclass(instances):
151
+ for qname, inst in instances.items():
152
+ td = get_type_def(qname)
153
+ assert td is not None
154
+ if td.cpp_formatter is not None:
155
+ args = inst.type_args
156
+ assert td.cpp_formatter(args) == inst.to_cpp(), (
157
+ f"{qname}: TypeDef.cpp_formatter({args!r})="
158
+ f"{td.cpp_formatter(args)!r} but "
159
+ f"instance.to_cpp()={inst.to_cpp()!r}"
160
+ )
161
+
162
+
163
+ def test_predicate_functions_agree_with_isinstance(instances):
164
+ from tpyc.type_def_registry import (
165
+ is_list, is_dict, is_set, is_array, is_span, is_dict_view,
166
+ is_range, is_iterator_adapter,
167
+ )
168
+ # No dedicated subclasses remain for nominal container types; all predicates
169
+ # are keyed on qname.
170
+ for qname, inst in instances.items():
171
+ assert is_set(inst) == (qname == "builtins.set"), (
172
+ f"is_set({qname}) = {is_set(inst)}"
173
+ )
174
+ assert is_dict(inst) == (qname == "builtins.dict"), (
175
+ f"is_dict({qname}) = {is_dict(inst)}"
176
+ )
177
+ assert is_array(inst) == (qname == "tpy.Array"), (
178
+ f"is_array({qname}) = {is_array(inst)}"
179
+ )
180
+ assert is_span(inst) == (qname == "tpy.Span"), (
181
+ f"is_span({qname}) = {is_span(inst)}"
182
+ )
183
+ assert is_range(inst) == (qname == "builtins.Range"), (
184
+ f"is_range({qname}) = {is_range(inst)}"
185
+ )
186
+ assert is_list(inst) == (qname == "builtins.list"), (
187
+ f"is_list({qname}) = {is_list(inst)}"
188
+ )
189
+ expected = qname in ("builtins.dict_keys", "builtins.dict_values", "builtins.dict_items")
190
+ assert is_dict_view(inst) == expected, (
191
+ f"is_dict_view({qname}) = {is_dict_view(inst)} but expected {expected}"
192
+ )
193
+ # iterator adapters: SpanIter + CopyIter + OwnIter (qname-based check)
194
+ for qname, inst in instances.items():
195
+ expected = qname in ("tpy.SpanIter", "tpy.CopyIter", "tpy.OwnIter")
196
+ assert is_iterator_adapter(inst) == expected, (
197
+ f"is_iterator_adapter({qname}) = {is_iterator_adapter(inst)} but expected {expected}"
198
+ )
199
+
200
+
201
+ def test_pending_types_are_not_concrete_containers():
202
+ """PendingListType / PendingDictType / PendingSetType share builtin qnames
203
+ with their resolved form ("builtins.list" etc.) but are distinct TpyType
204
+ subclasses with their own fields. `is_list` / `is_dict` / `is_set` must
205
+ reject them so call sites that access `type_args[0]` don't crash. Regression
206
+ test for the `list({})` crash (PendingDictType passing `is_dict`).
207
+ """
208
+ from tpyc.type_def_registry import is_list, is_dict, is_set
209
+ pending_list = ts.PendingListType(ts.INT32, 0, 0)
210
+ pending_dict = ts.PendingDictType(ts.STR, ts.INT32, 0)
211
+ pending_set = ts.PendingSetType(ts.INT32, 0)
212
+
213
+ # qnames match their resolved form (intentional) ...
214
+ assert pending_list.qualified_name() == "builtins.list"
215
+ assert pending_dict.qualified_name() == "builtins.dict"
216
+ assert pending_set.qualified_name() == "builtins.set"
217
+
218
+ # ... but the predicates must still reject them
219
+ assert not is_list(pending_list), "is_list must reject PendingListType"
220
+ assert not is_dict(pending_dict), "is_dict must reject PendingDictType"
221
+ assert not is_set(pending_set), "is_set must reject PendingSetType"
222
+
223
+
224
+ def test_pending_types_do_not_inherit_registry_behavior():
225
+ """Pending* types delegate `qualified_name()` to their resolved builtin,
226
+ so `type_def_of(pending_list)` returns the `list` TypeDef. But base
227
+ `TpyType` methods (subscript_borrows, needs_explicit_element_target,
228
+ is_value_type, is_expensive_copy, param_needs_copy_for_reassign)
229
+ must NOT inherit that registry behavior -- pending types haven't been
230
+ resolved yet and should report class defaults. Regression test for
231
+ the `_nominal_td` guard on the base class methods.
232
+ """
233
+ pending_list = ts.PendingListType(ts.INT32, 0, 0)
234
+ pending_dict = ts.PendingDictType(ts.STR, ts.INT32, 0)
235
+ pending_set = ts.PendingSetType(ts.INT32, 0)
236
+
237
+ resolved_list = ts.make_list(ts.INT32)
238
+ # Baseline: the resolved form DOES pick up registry behavior.
239
+ assert resolved_list.subscript_borrows() is True
240
+
241
+ # The pending form must NOT pick it up despite sharing the qname.
242
+ for pending in (pending_list, pending_dict, pending_set):
243
+ assert pending.subscript_borrows() is False, (
244
+ f"{type(pending).__name__}.subscript_borrows() leaked "
245
+ f"list/dict/set TypeDef value"
246
+ )
247
+ assert pending.needs_explicit_element_target() is False
248
+ assert pending.is_value_type() is False
249
+ assert pending.is_expensive_copy() is False
250
+ assert pending.param_needs_copy_for_reassign() is False
251
+
252
+
253
+ # =========================================================================
254
+ # Primitive hard-coded snapshot (Phase D step 0).
255
+ #
256
+ # Captures intrinsic per-qname behavior of every primitive currently
257
+ # implemented as a dedicated TpyType subclass. The snapshot is the golden
258
+ # reference for Phase D: as the subclasses are collapsed into NominalType +
259
+ # TypeDef, the instance methods start reading from the registry, but the
260
+ # hard-coded expected values here stay unchanged -- which makes this an
261
+ # independent validation that Phase D didn't drift behavior.
262
+ #
263
+ # Keep this table hand-written. Regenerating it from the instances would
264
+ # defeat the whole point (the check would pass by definition).
265
+ # =========================================================================
266
+
267
+
268
+ # Sentinel -- FStr.to_cpp() raises TypeError (compile-time only type).
269
+ _RAISES_TYPE_ERROR = object()
270
+
271
+
272
+ # Each entry pins the answers expected from the live TpyType instance *and*,
273
+ # eventually, from the TypeDef registry when the primitive becomes a plain
274
+ # NominalType. Fields mirror the current subclass overrides:
275
+ #
276
+ # category -- TypeCategory enum value
277
+ # is_value_type -- bool
278
+ # is_send / is_sync -- bool (default: == is_value_type)
279
+ # subscript_borrows -- bool (primitives: always False today)
280
+ # is_expensive_copy -- bool
281
+ # param_needs_copy_for_reassign-- bool
282
+ # is_compile_time_only -- bool
283
+ # to_cpp -- str or _RAISES_TYPE_ERROR
284
+ # to_cpp_param_type -- str (default: == to_cpp for value types)
285
+ # element_qname -- str | None (qname of get_element_type())
286
+ # int_bits / int_signed -- only for FIXED_INT category
287
+ # float_bits -- only for FLOAT category
288
+
289
+ PRIMITIVE_SNAPSHOT: dict[str, dict] = {
290
+ # --- Fixed-width integers --------------------------------------------
291
+ "tpy.Int8": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
292
+ is_send=True, is_sync=True, subscript_borrows=False,
293
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
294
+ is_compile_time_only=False,
295
+ to_cpp="int8_t", to_cpp_param_type="int8_t",
296
+ element_qname=None, int_bits=8, int_signed=True),
297
+ "tpy.Int16": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
298
+ is_send=True, is_sync=True, subscript_borrows=False,
299
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
300
+ is_compile_time_only=False,
301
+ to_cpp="int16_t", to_cpp_param_type="int16_t",
302
+ element_qname=None, int_bits=16, int_signed=True),
303
+ "tpy.Int32": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
304
+ is_send=True, is_sync=True, subscript_borrows=False,
305
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
306
+ is_compile_time_only=False,
307
+ to_cpp="int32_t", to_cpp_param_type="int32_t",
308
+ element_qname=None, int_bits=32, int_signed=True),
309
+ "tpy.Int64": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
310
+ is_send=True, is_sync=True, subscript_borrows=False,
311
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
312
+ is_compile_time_only=False,
313
+ to_cpp="int64_t", to_cpp_param_type="int64_t",
314
+ element_qname=None, int_bits=64, int_signed=True),
315
+ "tpy.UInt8": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
316
+ is_send=True, is_sync=True, subscript_borrows=False,
317
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
318
+ is_compile_time_only=False,
319
+ to_cpp="uint8_t", to_cpp_param_type="uint8_t",
320
+ element_qname=None, int_bits=8, int_signed=False),
321
+ "tpy.UInt16": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
322
+ is_send=True, is_sync=True, subscript_borrows=False,
323
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
324
+ is_compile_time_only=False,
325
+ to_cpp="uint16_t", to_cpp_param_type="uint16_t",
326
+ element_qname=None, int_bits=16, int_signed=False),
327
+ "tpy.UInt32": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
328
+ is_send=True, is_sync=True, subscript_borrows=False,
329
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
330
+ is_compile_time_only=False,
331
+ to_cpp="uint32_t", to_cpp_param_type="uint32_t",
332
+ element_qname=None, int_bits=32, int_signed=False),
333
+ "tpy.UInt64": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
334
+ is_send=True, is_sync=True, subscript_borrows=False,
335
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
336
+ is_compile_time_only=False,
337
+ to_cpp="uint64_t", to_cpp_param_type="uint64_t",
338
+ element_qname=None, int_bits=64, int_signed=False),
339
+
340
+ # --- Big int ---------------------------------------------------------
341
+ "builtins.int": dict(category=TypeCategory.BIG_INT, is_value_type=True,
342
+ is_send=True, is_sync=True, subscript_borrows=False,
343
+ is_expensive_copy=True, param_needs_copy_for_reassign=True,
344
+ is_compile_time_only=False,
345
+ to_cpp="::tpy::BigInt",
346
+ to_cpp_param_type="const ::tpy::BigInt&",
347
+ element_qname=None),
348
+
349
+ # --- Floats ----------------------------------------------------------
350
+ "builtins.float": dict(category=TypeCategory.FLOAT, is_value_type=True,
351
+ is_send=True, is_sync=True, subscript_borrows=False,
352
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
353
+ is_compile_time_only=False,
354
+ to_cpp="double", to_cpp_param_type="double",
355
+ element_qname=None, float_bits=64),
356
+ "tpy.Float32": dict(category=TypeCategory.FLOAT, is_value_type=True,
357
+ is_send=True, is_sync=True, subscript_borrows=False,
358
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
359
+ is_compile_time_only=False,
360
+ to_cpp="float", to_cpp_param_type="float",
361
+ element_qname=None, float_bits=32),
362
+
363
+ # --- Bool / Char -----------------------------------------------------
364
+ "builtins.bool": dict(category=TypeCategory.BOOL, is_value_type=True,
365
+ is_send=True, is_sync=True, subscript_borrows=False,
366
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
367
+ is_compile_time_only=False,
368
+ to_cpp="bool", to_cpp_param_type="bool",
369
+ element_qname=None),
370
+ "tpy.Char": dict(category=TypeCategory.CHAR, is_value_type=True,
371
+ is_send=True, is_sync=True, subscript_borrows=False,
372
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
373
+ is_compile_time_only=False,
374
+ to_cpp="char", to_cpp_param_type="char",
375
+ element_qname=None),
376
+
377
+ # --- String family ---------------------------------------------------
378
+ "builtins.str": dict(category=TypeCategory.STR, is_value_type=True,
379
+ is_send=True, is_sync=True, subscript_borrows=False,
380
+ is_expensive_copy=True, param_needs_copy_for_reassign=True,
381
+ is_compile_time_only=False,
382
+ to_cpp="std::string",
383
+ to_cpp_param_type="std::string_view",
384
+ element_qname="tpy.Char"),
385
+ "tpy.String": dict(category=TypeCategory.STR, is_value_type=True,
386
+ is_send=True, is_sync=True, subscript_borrows=False,
387
+ is_expensive_copy=True, param_needs_copy_for_reassign=True,
388
+ is_compile_time_only=False,
389
+ to_cpp="std::string",
390
+ to_cpp_param_type="const std::string&",
391
+ element_qname="tpy.Char"),
392
+ "tpy.StrView": dict(category=TypeCategory.STR, is_value_type=True,
393
+ is_send=False, is_sync=True, subscript_borrows=False,
394
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
395
+ is_compile_time_only=False,
396
+ to_cpp="std::string_view",
397
+ to_cpp_param_type="std::string_view",
398
+ element_qname="tpy.Char"),
399
+ "tpy.FStr": dict(category=TypeCategory.STR, is_value_type=True,
400
+ is_send=True, is_sync=True, subscript_borrows=False,
401
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
402
+ is_compile_time_only=True,
403
+ to_cpp=_RAISES_TYPE_ERROR, to_cpp_param_type=_RAISES_TYPE_ERROR,
404
+ element_qname=None),
405
+
406
+ # --- Bytes family ----------------------------------------------------
407
+ "builtins.bytes": dict(category=TypeCategory.BYTES, is_value_type=True,
408
+ is_send=True, is_sync=True, subscript_borrows=False,
409
+ is_expensive_copy=True, param_needs_copy_for_reassign=True,
410
+ is_compile_time_only=False,
411
+ to_cpp="std::vector<uint8_t>",
412
+ to_cpp_param_type="std::span<const uint8_t>",
413
+ element_qname="tpy.UInt8"),
414
+ "builtins.bytearray": dict(category=TypeCategory.BYTES, is_value_type=True,
415
+ is_send=True, is_sync=True, subscript_borrows=False,
416
+ is_expensive_copy=True, param_needs_copy_for_reassign=True,
417
+ is_compile_time_only=False,
418
+ to_cpp="std::vector<uint8_t>",
419
+ to_cpp_param_type="const std::vector<uint8_t>&",
420
+ element_qname="tpy.UInt8"),
421
+ "tpy.BytesView": dict(category=TypeCategory.BYTES, is_value_type=True,
422
+ is_send=False, is_sync=True, subscript_borrows=False,
423
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
424
+ is_compile_time_only=False,
425
+ to_cpp="std::span<const uint8_t>",
426
+ to_cpp_param_type="std::span<const uint8_t>",
427
+ element_qname="tpy.UInt8"),
428
+
429
+ # --- Slices ----------------------------------------------------------
430
+ "tpy.basic_slice": dict(category=TypeCategory.SLICE, is_value_type=True,
431
+ is_send=True, is_sync=True, subscript_borrows=False,
432
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
433
+ is_compile_time_only=False,
434
+ to_cpp="::tpy::BasicSlice",
435
+ to_cpp_param_type="::tpy::BasicSlice",
436
+ element_qname=None),
437
+ "builtins.slice": dict(category=TypeCategory.SLICE, is_value_type=True,
438
+ is_send=True, is_sync=True, subscript_borrows=False,
439
+ is_expensive_copy=False, param_needs_copy_for_reassign=False,
440
+ is_compile_time_only=False,
441
+ to_cpp="::tpy::Slice",
442
+ to_cpp_param_type="::tpy::Slice",
443
+ element_qname=None),
444
+ }
445
+
446
+
447
+ _PRIMITIVE_INSTANCES: dict[str, ts.TpyType] = {
448
+ "tpy.Int8": ts.INT8, "tpy.Int16": ts.INT16,
449
+ "tpy.Int32": ts.INT32, "tpy.Int64": ts.INT64,
450
+ "tpy.UInt8": ts.UINT8, "tpy.UInt16": ts.UINT16,
451
+ "tpy.UInt32": ts.UINT32, "tpy.UInt64": ts.UINT64,
452
+ "builtins.int": ts.BIGINT,
453
+ "builtins.float": ts.FLOAT,
454
+ "tpy.Float32": ts.FLOAT32,
455
+ "builtins.bool": ts.BOOL,
456
+ "tpy.Char": ts.CHAR,
457
+ "builtins.str": ts.STR,
458
+ "tpy.String": ts.STRING,
459
+ "tpy.StrView": ts.STRVIEW,
460
+ "tpy.FStr": ts.FSTR,
461
+ "builtins.bytes": ts.BYTES,
462
+ "builtins.bytearray": ts.BYTEARRAY,
463
+ "tpy.BytesView": ts.BYTESVIEW,
464
+ "tpy.basic_slice": ts.BASIC_SLICE,
465
+ "builtins.slice": ts.SLICE,
466
+ }
467
+
468
+
469
+ def test_primitive_snapshot_covers_every_registered_primitive():
470
+ """Every registry entry in a primitive-ish category must appear in the
471
+ snapshot. If a new primitive qname gets registered without being added
472
+ here, this test fails and the author must decide whether the new qname
473
+ is primitive (add to snapshot) or not (expand this ignore list)."""
474
+ primitive_categories = {
475
+ TypeCategory.FIXED_INT, TypeCategory.BIG_INT, TypeCategory.FLOAT,
476
+ TypeCategory.BOOL, TypeCategory.CHAR, TypeCategory.STR,
477
+ TypeCategory.BYTES, TypeCategory.SLICE,
478
+ }
479
+ registered_primitives = {
480
+ qn for qn, td in _type_defs.items() if td.category in primitive_categories
481
+ }
482
+ missing = registered_primitives - set(PRIMITIVE_SNAPSHOT)
483
+ assert not missing, (
484
+ f"Registered primitive qnames without a snapshot entry: {missing}. "
485
+ f"Add them to PRIMITIVE_SNAPSHOT with their expected per-qname behavior."
486
+ )
487
+ extra = set(PRIMITIVE_SNAPSHOT) - registered_primitives
488
+ assert not extra, (
489
+ f"PRIMITIVE_SNAPSHOT entries that aren't registered primitives: {extra}."
490
+ )
491
+
492
+
493
+ @pytest.mark.parametrize("qname", sorted(PRIMITIVE_SNAPSHOT))
494
+ def test_primitive_instance_matches_snapshot(qname):
495
+ """Hard-coded golden snapshot of every primitive's intrinsic behavior.
496
+ Validated against the live TpyType instance. Must keep passing through
497
+ Phase D even as the primitive subclasses collapse into NominalType."""
498
+ expected = PRIMITIVE_SNAPSHOT[qname]
499
+ inst = _PRIMITIVE_INSTANCES[qname]
500
+
501
+ # qualified_name must round-trip.
502
+ assert inst.qualified_name() == qname
503
+
504
+ # category comes from the TypeDef registry (by construction today,
505
+ # still true after migration). Included for completeness -- if the
506
+ # category ever drifts this catches it.
507
+ td = get_type_def(qname)
508
+ assert td is not None
509
+ assert td.category is expected["category"], (
510
+ f"{qname}: TypeDef.category={td.category!r} "
511
+ f"but snapshot expects {expected['category']!r}"
512
+ )
513
+
514
+ assert inst.is_value_type() == expected["is_value_type"], qname
515
+ assert inst.is_send() == expected["is_send"], qname
516
+ assert inst.is_sync() == expected["is_sync"], qname
517
+ assert inst.subscript_borrows() == expected["subscript_borrows"], qname
518
+ assert inst.is_expensive_copy() == expected["is_expensive_copy"], qname
519
+ assert inst.param_needs_copy_for_reassign() == expected["param_needs_copy_for_reassign"], qname
520
+ assert inst.is_compile_time_only() == expected["is_compile_time_only"], qname
521
+
522
+ if expected["to_cpp"] is _RAISES_TYPE_ERROR:
523
+ with pytest.raises(TypeError):
524
+ inst.to_cpp()
525
+ else:
526
+ assert inst.to_cpp() == expected["to_cpp"], qname
527
+
528
+ if expected["to_cpp_param_type"] is _RAISES_TYPE_ERROR:
529
+ with pytest.raises(TypeError):
530
+ inst.to_cpp_param_type()
531
+ else:
532
+ assert inst.to_cpp_param_type() == expected["to_cpp_param_type"], qname
533
+
534
+ elem = inst.get_element_type()
535
+ if expected["element_qname"] is None:
536
+ assert elem is None, f"{qname}: expected no element, got {elem!r}"
537
+ else:
538
+ assert elem is not None, f"{qname}: expected element, got None"
539
+ assert elem.qualified_name() == expected["element_qname"], qname
540
+
541
+ # Category-specific checks.
542
+ if expected["category"] is TypeCategory.FIXED_INT:
543
+ # Post-Phase-D: bits/signed live in TypeDef.int_traits, reached via
544
+ # int_traits_of(inst). Pin the accessor so Phase D can't silently
545
+ # break the lookup for any caller.
546
+ from tpyc.type_def_registry import int_traits_of
547
+ tr = int_traits_of(inst)
548
+ assert tr is not None, qname
549
+ assert tr.bits == expected["int_bits"], qname
550
+ assert tr.signed == expected["int_signed"], qname
551
+
552
+
553
+ @pytest.mark.parametrize("qname", sorted(PRIMITIVE_SNAPSHOT))
554
+ def test_primitive_type_def_matches_snapshot(qname):
555
+ """The TypeDef registry entry for each primitive qname must carry the
556
+ snapshot values directly (independent of any TpyType subclass). This
557
+ is the forward-facing half of Phase D validation: Step 1 enriches
558
+ TypeDef with these fields; later steps flip callers to read them. The
559
+ subclass check above and this check both compare against the same
560
+ PRIMITIVE_SNAPSHOT, so if a caller reads TypeDef and drifts from
561
+ observed behavior the pair of tests catches it."""
562
+ expected = PRIMITIVE_SNAPSHOT[qname]
563
+ td = get_type_def(qname)
564
+ assert td is not None, qname
565
+ assert td.category is expected["category"], qname
566
+ assert td.is_value_type == expected["is_value_type"], qname
567
+ assert td.subscript_borrows == expected["subscript_borrows"], qname
568
+ assert td.is_expensive_copy == expected["is_expensive_copy"], qname
569
+ assert td.param_needs_copy_for_reassign == expected["param_needs_copy_for_reassign"], qname
570
+ assert td.is_compile_time_only == expected["is_compile_time_only"], qname
571
+
572
+ # is_send / is_sync: default None means "follow is_value_type". Resolve
573
+ # against empty type_args (primitives have none) and compare.
574
+ send_resolved = resolve_send_sync(td.is_send, ())
575
+ expected_send = expected["is_send"]
576
+ if send_resolved is None:
577
+ assert td.is_value_type == expected_send, qname
578
+ else:
579
+ assert send_resolved == expected_send, qname
580
+ sync_resolved = resolve_send_sync(td.is_sync, ())
581
+ expected_sync = expected["is_sync"]
582
+ if sync_resolved is None:
583
+ assert td.is_value_type == expected_sync, qname
584
+ else:
585
+ assert sync_resolved == expected_sync, qname
586
+
587
+ # cpp_formatter: populated for every primitive except FStr (compile-
588
+ # time only -- its to_cpp raises TypeError, which is an absence of
589
+ # formatter rather than a concrete string).
590
+ if expected["to_cpp"] is _RAISES_TYPE_ERROR:
591
+ assert td.cpp_formatter is None, qname
592
+ else:
593
+ assert td.cpp_formatter is not None, qname
594
+ assert td.cpp_formatter(()) == expected["to_cpp"], qname
595
+
596
+ if expected["to_cpp_param_type"] is _RAISES_TYPE_ERROR:
597
+ assert td.param_cpp_formatter is None, qname
598
+ else:
599
+ assert td.param_cpp_formatter is not None, qname
600
+ assert td.param_cpp_formatter(()) == expected["to_cpp_param_type"], qname
601
+
602
+ if expected["category"] is TypeCategory.FIXED_INT:
603
+ assert td.int_traits is not None, qname
604
+ assert td.int_traits.bits == expected["int_bits"], qname
605
+ assert td.int_traits.signed == expected["int_signed"], qname
606
+ else:
607
+ assert td.int_traits is None, qname
608
+
609
+ if expected["category"] is TypeCategory.FLOAT:
610
+ assert td.float_traits is not None, qname
611
+ assert td.float_traits.bits == expected["float_bits"], qname
612
+ else:
613
+ assert td.float_traits is None, qname
614
+
615
+
616
+ def test_primitive_predicates_match_isinstance():
617
+ """Every primitive predicate returns True for exactly one canonical qname
618
+ (or, for category predicates like is_fixed_int_type, for exactly the
619
+ expected set of qnames). Post-Phase D the primitive subclasses are gone,
620
+ so the historical isinstance arm of this test is empty; the qname-based
621
+ checks below are what pins predicate correctness today."""
622
+ from tpyc.type_def_registry import (
623
+ is_fixed_int_type, is_big_int_type, is_bool_type, is_char_type,
624
+ is_float_category, is_str_category, is_bytes_category, is_slice_category,
625
+ is_str_type, is_string_type, is_str_view_type, is_fstr_type,
626
+ is_float64_type, is_float32_type,
627
+ is_bytes_type, is_bytearray_type, is_bytes_view_type,
628
+ )
629
+ # After Phase D step 6: all primitive subclasses are NominalType singletons.
630
+ # Validate the qname-only predicates directly (no isinstance-equivalent).
631
+ qname_preds = {
632
+ is_fstr_type: "tpy.FStr",
633
+ is_str_type: "builtins.str",
634
+ is_string_type: "tpy.String",
635
+ is_str_view_type: "tpy.StrView",
636
+ is_bytes_type: "builtins.bytes",
637
+ is_bytearray_type: "builtins.bytearray",
638
+ is_bytes_view_type: "tpy.BytesView",
639
+ is_bool_type: "builtins.bool",
640
+ is_char_type: "tpy.Char",
641
+ is_float64_type: "builtins.float",
642
+ is_float32_type: "tpy.Float32",
643
+ is_big_int_type: "builtins.int",
644
+ }
645
+ # Fixed-int qnames span Int8..UInt64; is_fixed_int_type is a category
646
+ # predicate, so check it membership-style instead of qname-equality.
647
+ fixed_int_qnames = {
648
+ "tpy.Int8", "tpy.Int16", "tpy.Int32", "tpy.Int64",
649
+ "tpy.UInt8", "tpy.UInt16", "tpy.UInt32", "tpy.UInt64",
650
+ }
651
+ for qname, inst in _PRIMITIVE_INSTANCES.items():
652
+ assert is_fixed_int_type(inst) == (qname in fixed_int_qnames), (
653
+ f"is_fixed_int_type({qname}) = {is_fixed_int_type(inst)}"
654
+ )
655
+ for pred, expected_qname in qname_preds.items():
656
+ for qname, inst in _PRIMITIVE_INSTANCES.items():
657
+ assert pred(inst) == (qname == expected_qname), (
658
+ f"{pred.__name__}({qname}) = {pred(inst)}"
659
+ )
660
+
661
+ # Category predicates: verify against snapshot category.
662
+ cat_preds = {
663
+ TypeCategory.FIXED_INT: is_fixed_int_type,
664
+ TypeCategory.BIG_INT: is_big_int_type,
665
+ TypeCategory.BOOL: is_bool_type,
666
+ TypeCategory.CHAR: is_char_type,
667
+ TypeCategory.FLOAT: is_float_category,
668
+ TypeCategory.STR: is_str_category,
669
+ TypeCategory.BYTES: is_bytes_category,
670
+ TypeCategory.SLICE: is_slice_category,
671
+ }
672
+ for qname, expected in PRIMITIVE_SNAPSHOT.items():
673
+ inst = _PRIMITIVE_INSTANCES[qname]
674
+ for cat, pred in cat_preds.items():
675
+ assert pred(inst) == (expected["category"] is cat), (
676
+ f"{pred.__name__}({qname}) = {pred(inst)} but "
677
+ f"category is {expected['category']}"
678
+ )
679
+
680
+
681
+ def test_trait_accessors_match_snapshot():
682
+ """int_traits_of / float_traits_of must return the expected traits for
683
+ category-appropriate primitives, None otherwise."""
684
+ from tpyc.type_def_registry import int_traits_of, float_traits_of
685
+ for qname, expected in PRIMITIVE_SNAPSHOT.items():
686
+ inst = _PRIMITIVE_INSTANCES[qname]
687
+ int_tr = int_traits_of(inst)
688
+ float_tr = float_traits_of(inst)
689
+ if expected["category"] is TypeCategory.FIXED_INT:
690
+ assert int_tr is not None
691
+ assert int_tr.bits == expected["int_bits"], qname
692
+ assert int_tr.signed == expected["int_signed"], qname
693
+ else:
694
+ assert int_tr is None, qname
695
+ if expected["category"] is TypeCategory.FLOAT:
696
+ assert float_tr is not None
697
+ assert float_tr.bits == expected["float_bits"], qname
698
+ else:
699
+ assert float_tr is None, qname
700
+
701
+
702
+ def test_is_any_str_type_excludes_fstr():
703
+ """is_any_str_type must not report FStr as a string type even though FStr
704
+ shares TypeCategory.STR with the runtime str family in the registry.
705
+ FStr is compile-time only and is never a valid str value at runtime --
706
+ callers relying on is_any_str_type to branch on runtime str types would
707
+ misbehave if FStr slipped in."""
708
+ assert not ts.is_any_str_type(ts.FSTR)
709
+ # Sanity: the legitimate str-family types still report True.
710
+ assert ts.is_any_str_type(ts.STR)
711
+ assert ts.is_any_str_type(ts.STRING)
712
+ assert ts.is_any_str_type(ts.STRVIEW)
713
+
714
+
715
+ # =========================================================================
716
+ # Enum hard-coded snapshot (Phase E).
717
+ #
718
+ # EnumType and IntEnumType are the last nominal-type subclasses. Before
719
+ # collapsing them into NominalType + TypeDef.enum, pin their intrinsic
720
+ # behavior so the migration path has an independent reference: this
721
+ # snapshot is hand-written expected values, not derived from the subclass
722
+ # methods, and must keep passing through every migration step (add
723
+ # TypeDef.enum -> populate on registration -> flip readers -> delete
724
+ # subclass). Mirrors PRIMITIVE_SNAPSHOT's role for Phase D.
725
+ # =========================================================================
726
+
727
+
728
+ def _build_enum_snapshot_instances() -> dict[str, "ts.NominalType"]:
729
+ """Build sample enum instances for the snapshot.
730
+
731
+ Mirrors what sema/registration.py does: each enum is a NominalType with
732
+ `_module_qname` set (so `type_def_of(t)` resolves), and its EnumInfo
733
+ payload is attached to the TypeDef registry so `enum_info_of(t)`
734
+ returns the members / underlying_type / is_int_enum data. Covers:
735
+ qualified + unqualified (mimicking `__main__`) plain enums, and
736
+ IntEnum variants with default Int32 and non-default UInt8 underlyings.
737
+ """
738
+ from tpyc.type_def_registry import (
739
+ attach_dynamic_type_def, TypeCategory, EnumInfo as _EnumInfo,
740
+ )
741
+ fixtures = [
742
+ # (short name, module_name for qname, module_name on EnumInfo, is_int_enum, members, underlying)
743
+ ("Color", "palette", "palette", False,
744
+ (("Red", 0), ("Green", 1), ("Blue", 2)), ts.INT32),
745
+ # module_name=None on the EnumInfo (matches __main__ enum), but the
746
+ # qname uses "__main__" as prefix so TypeDef lookup still works.
747
+ ("Status", "__main__", None, False,
748
+ (("Ok", 0), ("Err", 1)), ts.INT32),
749
+ ("Level", "log", "log", True,
750
+ (("Low", 0), ("High", 1)), ts.INT32),
751
+ ("Flags", "perms", "perms", True,
752
+ (("Read", 1), ("Write", 2), ("Exec", 4)), ts.UINT8),
753
+ ]
754
+ result: dict[str, ts.NominalType] = {}
755
+ for name, qname_module, info_module, is_int_enum, member_values, underlying in fixtures:
756
+ members = tuple(m for m, _ in member_values)
757
+ qname = f"{qname_module}.{name}"
758
+ t = ts.NominalType(name=name, type_args=(), _module_qname=qname)
759
+ attach_dynamic_type_def(
760
+ qname, TypeCategory.ENUM,
761
+ enum=_EnumInfo(
762
+ members=members,
763
+ member_values=member_values,
764
+ underlying_type=underlying,
765
+ is_int_enum=is_int_enum,
766
+ module_name=info_module,
767
+ ),
768
+ is_value_type=True,
769
+ )
770
+ result[name] = t
771
+ return result
772
+
773
+
774
+ # Hand-written expected values -- do NOT derive from the instance.
775
+ # The point of the snapshot is that these answers survive the migration
776
+ # from subclass fields to TypeDef.enum.
777
+ ENUM_SNAPSHOT: dict[str, dict] = {
778
+ "Color": dict(
779
+ is_int_enum=False,
780
+ members=("Red", "Green", "Blue"),
781
+ member_values=(("Red", 0), ("Green", 1), ("Blue", 2)),
782
+ underlying_qname="tpy.Int32",
783
+ qualified_name="palette.Color",
784
+ info_module_name="palette",
785
+ to_cpp="Color",
786
+ is_value_type=True, is_send=True, is_sync=True,
787
+ subscript_borrows=False, is_expensive_copy=False,
788
+ ),
789
+ "Status": dict(
790
+ is_int_enum=False,
791
+ members=("Ok", "Err"),
792
+ member_values=(("Ok", 0), ("Err", 1)),
793
+ underlying_qname="tpy.Int32",
794
+ qualified_name="__main__.Status",
795
+ info_module_name=None, # __main__ enums preserve None on EnumInfo.module_name
796
+ to_cpp="Status",
797
+ is_value_type=True, is_send=True, is_sync=True,
798
+ subscript_borrows=False, is_expensive_copy=False,
799
+ ),
800
+ "Level": dict(
801
+ is_int_enum=True,
802
+ members=("Low", "High"),
803
+ member_values=(("Low", 0), ("High", 1)),
804
+ underlying_qname="tpy.Int32",
805
+ qualified_name="log.Level",
806
+ info_module_name="log",
807
+ to_cpp="Level",
808
+ is_value_type=True, is_send=True, is_sync=True,
809
+ subscript_borrows=False, is_expensive_copy=False,
810
+ ),
811
+ "Flags": dict(
812
+ is_int_enum=True,
813
+ members=("Read", "Write", "Exec"),
814
+ member_values=(("Read", 1), ("Write", 2), ("Exec", 4)),
815
+ underlying_qname="tpy.UInt8",
816
+ qualified_name="perms.Flags",
817
+ info_module_name="perms",
818
+ to_cpp="Flags",
819
+ is_value_type=True, is_send=True, is_sync=True,
820
+ subscript_borrows=False, is_expensive_copy=False,
821
+ ),
822
+ }
823
+
824
+
825
+ @pytest.mark.parametrize("name", sorted(ENUM_SNAPSHOT))
826
+ def test_enum_instance_matches_snapshot(name):
827
+ """Pin enum behavior against hand-coded expected answers. Post-Phase-E
828
+ enums are NominalType + TypeDef.enum; accessor data is read via
829
+ `enum_info_of(t)`."""
830
+ from tpyc.type_def_registry import (
831
+ enum_info_of, is_enum_type, is_int_enum_type,
832
+ )
833
+ expected = ENUM_SNAPSHOT[name]
834
+ instances = _build_enum_snapshot_instances()
835
+ inst = instances[name]
836
+ info = enum_info_of(inst)
837
+ assert info is not None, f"enum_info_of({name}) returned None"
838
+
839
+ # Structural identity.
840
+ assert inst.name == name
841
+ assert info.members == expected["members"]
842
+ assert info.member_values == expected["member_values"]
843
+ assert info.underlying_type.qualified_name() == expected["underlying_qname"]
844
+ assert inst.qualified_name() == expected["qualified_name"]
845
+ assert info.module_name == expected["info_module_name"]
846
+
847
+ # IntEnum is distinguished by the is_int_enum flag on EnumInfo.
848
+ assert info.is_int_enum == expected["is_int_enum"]
849
+ assert is_int_enum_type(inst) == expected["is_int_enum"]
850
+ assert is_enum_type(inst)
851
+
852
+ # Behavior (inherited from NominalType, resolved via TypeDef).
853
+ assert inst.is_value_type() == expected["is_value_type"]
854
+ assert inst.is_send() == expected["is_send"]
855
+ assert inst.is_sync() == expected["is_sync"]
856
+ assert inst.subscript_borrows() == expected["subscript_borrows"]
857
+ assert inst.is_expensive_copy() == expected["is_expensive_copy"]
858
+ assert inst.to_cpp() == expected["to_cpp"]
859
+
860
+ # member_value_map should round-trip from member_values.
861
+ assert info.member_value_map == dict(expected["member_values"])
862
+
863
+
864
+ def test_enum_predicates_reject_non_enum_types():
865
+ """enum_info_of / is_enum_type / is_int_enum_type must return None / False
866
+ (not crash) on non-enum inputs. Covers the common cases: primitives,
867
+ containers, structural wrappers, and the tricky LiteralType / PendingView
868
+ siblings whose qualified_name delegates to a base type's qname."""
869
+ from tpyc.type_def_registry import enum_info_of, is_enum_type, is_int_enum_type
870
+ non_enum_cases = [
871
+ ts.INT32, ts.BIGINT, ts.BOOL, ts.STR, ts.FLOAT,
872
+ ts.make_list(ts.INT32),
873
+ ts.make_dict(ts.STR, ts.INT32),
874
+ ts.make_range(ts.INT32),
875
+ ts.OptionalType(ts.INT32),
876
+ ts.TupleType((ts.INT32, ts.STR)),
877
+ ]
878
+ for t in non_enum_cases:
879
+ assert enum_info_of(t) is None, f"enum_info_of({t}) should be None"
880
+ assert not is_enum_type(t), f"is_enum_type({t}) should be False"
881
+ assert not is_int_enum_type(t), f"is_int_enum_type({t}) should be False"
882
+
883
+
884
+ def test_fixed_int_range_bounds_match_width():
885
+ """min_value / max_value are derived from (bits, signed). Pin the
886
+ formula against a few representative widths so Phase D can't silently
887
+ change the range computation."""
888
+ cases = [
889
+ ("tpy.Int8", -2**7, 2**7 - 1),
890
+ ("tpy.Int16", -2**15, 2**15 - 1),
891
+ ("tpy.Int32", -2**31, 2**31 - 1),
892
+ ("tpy.Int64", -2**63, 2**63 - 1),
893
+ ("tpy.UInt8", 0, 2**8 - 1),
894
+ ("tpy.UInt16", 0, 2**16 - 1),
895
+ ("tpy.UInt32", 0, 2**32 - 1),
896
+ ("tpy.UInt64", 0, 2**64 - 1),
897
+ ]
898
+ for qname, lo, hi in cases:
899
+ td = get_type_def(qname)
900
+ assert td is not None and td.int_traits is not None
901
+ assert td.int_traits.min_value == lo, f"{qname}"
902
+ assert td.int_traits.max_value == hi, f"{qname}"
903
+
904
+
905
+ def test_type_matches_numeric_rejects_cross_container():
906
+ """Post-Phase-D all builtin containers (list, set, dict, Array, Span, ...)
907
+ share the NominalType class, so `type(arg) == type(param)` is True for any
908
+ pair of containers. type_matches_numeric must also compare the container
909
+ name -- otherwise resolve_overload could pick a set[Int32] overload for a
910
+ list[Int32] argument via the recursive element-matching branch."""
911
+ from tpyc.sema.overloads import type_matches_numeric
912
+ assert not type_matches_numeric(ts.make_list(ts.INT32), ts.make_set(ts.INT32))
913
+ assert not type_matches_numeric(ts.make_set(ts.INT32), ts.make_list(ts.INT32))
914
+ assert not type_matches_numeric(
915
+ ts.make_dict(ts.STR, ts.INT32),
916
+ ts.make_list(ts.INT32),
917
+ )
918
+ # Positive case: same container with IntLiteral element should still match
919
+ # the concrete-int overload via the element-recursion branch.
920
+ lit = ts.IntLiteralType(value=5)
921
+ assert type_matches_numeric(ts.make_list(lit), ts.make_list(ts.INT32))
922
+
923
+
924
+ def test_structural_match_rejects_cross_container():
925
+ """Same anti-pattern as type_matches_numeric but in _structural_match
926
+ (pass-1 overload resolution). Post-Phase-D list/set/Array/Span are all
927
+ NominalType, so `type(x) != type(y)` returns False and the recursive
928
+ inner-type match would fire -- needs a name comparison too."""
929
+ from tpyc.sema.overloads import _structural_match
930
+ assert not _structural_match(ts.make_list(ts.INT32), ts.make_set(ts.INT32))
931
+ assert not _structural_match(ts.make_set(ts.INT32), ts.make_list(ts.INT32))
932
+ # Positive case: exact match still works.
933
+ assert _structural_match(ts.make_list(ts.INT32), ts.make_list(ts.INT32))
934
+
935
+
936
+ # =========================================================================
937
+ # Dynamic TypeDef attachment (Phase E step 2).
938
+ #
939
+ # sema/registration.py attaches RecordInfo / ProtocolInfo onto the TypeDef
940
+ # registry as records and protocols come through register_record /
941
+ # register_protocol. The tests here pin the attach-and-clear behavior in
942
+ # isolation, without going through a full compile. The conftest autouse
943
+ # fixture calls clear_all_compilation_state() around every test, so each
944
+ # case starts from a clean dynamic slate.
945
+ # =========================================================================
946
+
947
+
948
+ def test_attach_dynamic_creates_new_typedef():
949
+ """Purely-dynamic qname (no static entry): attach creates a TypeDef."""
950
+ from tpyc.type_def_registry import attach_dynamic_type_def, clear_dynamic_type_defs
951
+ qname = "test_module.TestProtocol"
952
+ assert get_type_def(qname) is None
953
+ stub = object() # opaque ProtocolInfo stand-in
954
+ td = attach_dynamic_type_def(qname, TypeCategory.PROTOCOL, protocol=stub)
955
+ assert td is get_type_def(qname)
956
+ assert td.category is TypeCategory.PROTOCOL
957
+ assert td.protocol is stub
958
+ assert td.record is None
959
+ clear_dynamic_type_defs()
960
+ assert get_type_def(qname) is None
961
+
962
+
963
+ def test_record_and_protocol_attach_during_compile():
964
+ """End-to-end: compile a module with a user protocol and verify that the
965
+ TypeDef registry has the ProtocolInfo attached under its qname. The
966
+ conformance tests alone don't hit the sema hook -- this pins that the
967
+ hook actually fires for real compiles. Looks up the TypeDef by qname
968
+ directly so the assertion doesn't depend on NominalType.qualified_name()
969
+ walking the `_protocol_modules` side-channel.
970
+
971
+ Note: `Compiler.from_source` compiles the source as the entry point, so
972
+ its runtime module identity is `__main__` regardless of the `module_name`
973
+ argument (which only names the generated file). Protocol registration
974
+ uses the runtime module, so the qname falls into the `__main__.<Name>`
975
+ branch of register_protocol."""
976
+ from tpyc import get_lib_dir
977
+ from tpyc.compiler import Compiler
978
+ source = (
979
+ "from typing import Protocol\n"
980
+ "class Greeter(Protocol):\n"
981
+ " def greet(self) -> str: ...\n"
982
+ )
983
+ compiler = Compiler.from_source(source,
984
+ lib_dirs=[get_lib_dir() / "tpy"])
985
+ compiler.compile()
986
+ # Keep the compilation's dynamic_type_defs slice visible while we
987
+ # inspect the registry post-compile.
988
+ with activate_compiler(compiler):
989
+ td = get_type_def("__main__.Greeter")
990
+ assert td is not None, (
991
+ "TypeDef for '__main__.Greeter' should exist after sema registers the protocol."
992
+ )
993
+ assert td.category is TypeCategory.PROTOCOL
994
+ assert td.protocol is not None, "TypeDef.protocol payload should be attached"
995
+ assert td.protocol.name == "Greeter"
996
+ assert any(m.name == "greet" for m in td.protocol.methods)
997
+
998
+
999
+ def test_attach_dynamic_updates_existing_typedef():
1000
+ """Pre-existing static qname (e.g. builtins.list): attach updates only the
1001
+ record/protocol payload; category and cpp_formatter are preserved."""
1002
+ from tpyc.type_def_registry import attach_dynamic_type_def, clear_dynamic_type_defs
1003
+ td_before = get_type_def("builtins.list")
1004
+ assert td_before is not None
1005
+ assert td_before.category is TypeCategory.LIST
1006
+ original_category = td_before.category
1007
+ original_cpp_formatter = td_before.cpp_formatter
1008
+ original_record = td_before.record
1009
+
1010
+ stub = object()
1011
+ attach_dynamic_type_def("builtins.list", TypeCategory.RECORD, record=stub)
1012
+ td_after = get_type_def("builtins.list")
1013
+
1014
+ # Category and cpp_formatter survive the attachment -- only payloads change.
1015
+ assert td_after.category is original_category
1016
+ assert td_after.cpp_formatter is original_cpp_formatter
1017
+ assert td_after.record is stub
1018
+
1019
+ clear_dynamic_type_defs()
1020
+ td_cleared = get_type_def("builtins.list")
1021
+ # Static TypeDef remains; record payload resets to the pre-attach value.
1022
+ assert td_cleared is not None
1023
+ assert td_cleared.category is original_category
1024
+ assert td_cleared.record is original_record
1025
+
1026
+
1027
+ # =========================================================================
1028
+ # Factory payload snapshot (Phase F.3e).
1029
+ #
1030
+ # Hard-coded golden table of `param_kinds` arity + kind for every factory
1031
+ # entry that used to live in `modules/type_resolution.py`. Pins the
1032
+ # contract that the merged TypeDef registry must honor. As with
1033
+ # PRIMITIVE_SNAPSHOT, hand-maintained -- regenerating from the registry
1034
+ # would defeat the check.
1035
+ # =========================================================================
1036
+
1037
+
1038
+ def _k(kind_name: str):
1039
+ """Shorthand for TypeParamKind enum members (imported lazily to avoid
1040
+ adding a top-level typesys import to this file)."""
1041
+ from tpyc.typesys import TypeParamKind
1042
+ return getattr(TypeParamKind, kind_name)
1043
+
1044
+
1045
+ # qname -> tuple of TypeParamKind strings ("TYPE" / "INT"). Empty tuple
1046
+ # means "no type args" (primitive singletons).
1047
+ FACTORY_SNAPSHOT: dict[str, tuple[str, ...]] = {
1048
+ # Containers
1049
+ "builtins.list": ("TYPE",),
1050
+ "builtins.dict": ("TYPE", "TYPE"),
1051
+ "builtins.dict_keys": ("TYPE", "TYPE"),
1052
+ "builtins.dict_values": ("TYPE", "TYPE"),
1053
+ "builtins.dict_items": ("TYPE", "TYPE"),
1054
+ "builtins.set": ("TYPE",),
1055
+ "builtins.Range": ("TYPE",),
1056
+ "tpy.Array": ("TYPE", "INT"),
1057
+ "tpy.Span": ("TYPE",),
1058
+ "tpy.SpanIter": ("TYPE",),
1059
+ "tpy.coro.Waker": (),
1060
+ # Structural wrapper
1061
+ "tpy.Ptr": ("TYPE",),
1062
+ # Primitive singletons
1063
+ "tpy.Float32": (),
1064
+ "tpy.Char": (),
1065
+ "tpy.String": (),
1066
+ "tpy.StrView": (),
1067
+ "tpy.FStr": (),
1068
+ "builtins.int": (),
1069
+ "builtins.float": (),
1070
+ "builtins.bool": (),
1071
+ "builtins.str": (),
1072
+ "builtins.bytes": (),
1073
+ "builtins.bytearray": (),
1074
+ "tpy.BytesView": (),
1075
+ "tpy.basic_slice": (),
1076
+ "builtins.slice": (),
1077
+ "tpy.Int8": (), "tpy.Int16": (), "tpy.Int32": (), "tpy.Int64": (),
1078
+ "tpy.UInt8": (), "tpy.UInt16": (), "tpy.UInt32": (), "tpy.UInt64": (),
1079
+ }
1080
+
1081
+
1082
+ def test_factory_snapshot_covers_every_registered_factory():
1083
+ """Every TypeDef with a `type_factory` must have a FACTORY_SNAPSHOT
1084
+ entry, and vice versa. Protects against silent drift when someone adds
1085
+ or removes a factory without updating the golden table."""
1086
+ registered = {qn for qn, td in _type_defs.items() if td.type_factory is not None}
1087
+ missing = registered - set(FACTORY_SNAPSHOT)
1088
+ assert not missing, (
1089
+ f"Registered factory qnames without a FACTORY_SNAPSHOT entry: {missing}."
1090
+ )
1091
+ extra = set(FACTORY_SNAPSHOT) - registered
1092
+ assert not extra, (
1093
+ f"FACTORY_SNAPSHOT entries that aren't registered factories: {extra}."
1094
+ )
1095
+
1096
+
1097
+ @pytest.mark.parametrize("qname", sorted(FACTORY_SNAPSHOT))
1098
+ def test_factory_param_kinds_match_snapshot(qname):
1099
+ """`TypeDef.param_kinds` must match the golden arity/kinds table."""
1100
+ td = get_type_def(qname)
1101
+ assert td is not None
1102
+ expected = tuple(_k(name) for name in FACTORY_SNAPSHOT[qname])
1103
+ actual = tuple(td.param_kinds)
1104
+ assert actual == expected, (
1105
+ f"{qname}: TypeDef.param_kinds={actual!r} but snapshot={expected!r}"
1106
+ )
1107
+
1108
+
1109
+ def test_factory_produces_same_instance_as_registry():
1110
+ """`TypeDef.type_factory(...)` with canonical args must produce a
1111
+ TpyType whose qualified name equals the TypeDef qname. Catches wiring
1112
+ mistakes (e.g. list's factory producing a dict) without pinning every
1113
+ factory's exact return value."""
1114
+ I32 = ts.INT32
1115
+ canonical_args: dict[str, tuple] = {
1116
+ "builtins.list": (I32,),
1117
+ "builtins.dict": (ts.STR, I32),
1118
+ "builtins.dict_keys": (ts.STR, I32),
1119
+ "builtins.dict_values": (ts.STR, I32),
1120
+ "builtins.dict_items": (ts.STR, I32),
1121
+ "builtins.set": (I32,),
1122
+ "builtins.Range": (I32,),
1123
+ "tpy.Array": (I32, 10),
1124
+ "tpy.Span": (I32,),
1125
+ "tpy.SpanIter": (I32,),
1126
+ "tpy.Ptr": (I32,),
1127
+ }
1128
+ for qname, args in canonical_args.items():
1129
+ td = get_type_def(qname)
1130
+ assert td is not None and td.type_factory is not None
1131
+ produced = td.type_factory(*args)
1132
+ assert produced.qualified_name() == qname, (
1133
+ f"{qname}: factory produced {produced!r} with qname "
1134
+ f"{produced.qualified_name()!r}"
1135
+ )
1136
+
1137
+ # Zero-arg factories must return the typesys singleton with the same qname.
1138
+ zero_arg_qnames = [qn for qn, kinds in FACTORY_SNAPSHOT.items() if not kinds]
1139
+ for qname in zero_arg_qnames:
1140
+ td = get_type_def(qname)
1141
+ assert td is not None and td.type_factory is not None
1142
+ produced = td.type_factory()
1143
+ assert produced.qualified_name() == qname, (
1144
+ f"{qname}: zero-arg factory produced {produced!r} with qname "
1145
+ f"{produced.qualified_name()!r}"
1146
+ )
1147
+
1148
+
1149
+ def test_structural_wrapper_category_only_holds_ptr():
1150
+ """The STRUCTURAL_WRAPPER category exists only for `tpy.Ptr`. If a new
1151
+ entry gets added, the author must update the resolver / sema paths
1152
+ that currently key on Ptr being the sole structural-wrapper factory."""
1153
+ wrappers = {qn for qn, td in _type_defs.items()
1154
+ if td.category is TypeCategory.STRUCTURAL_WRAPPER}
1155
+ assert wrappers == {"tpy.Ptr"}, (
1156
+ f"Unexpected STRUCTURAL_WRAPPER entries: {wrappers}"
1157
+ )
1158
+
1159
+
1160
+ def test_find_factory_helpers_match_old_lookup():
1161
+ """`find_factory_by_simple_name` and `find_factory_in_module` must
1162
+ resolve to the same qname as the old `lookup_generic_type` /
1163
+ `lookup_generic_type_in_module` factory table."""
1164
+ from tpyc.type_def_registry import (
1165
+ find_factory_by_simple_name, find_factory_in_module,
1166
+ factory_qnames_in_module,
1167
+ )
1168
+ # Simple-name lookup scans builtins then tpy.
1169
+ assert find_factory_by_simple_name("list").qname == "builtins.list"
1170
+ assert find_factory_by_simple_name("Span").qname == "tpy.Span"
1171
+ assert find_factory_by_simple_name("Ptr").qname == "tpy.Ptr"
1172
+ assert find_factory_by_simple_name("definitely_unknown") is None
1173
+
1174
+ # Module-qualified lookup.
1175
+ assert find_factory_in_module("Array", "tpy").qname == "tpy.Array"
1176
+ assert find_factory_in_module("list", "builtins").qname == "builtins.list"
1177
+ # Wrong module -> None.
1178
+ assert find_factory_in_module("Array", "builtins") is None
1179
+ assert find_factory_in_module("list", "tpy") is None
1180
+
1181
+ # Enumeration helper: every tpy.* factory qname must appear.
1182
+ tpy_factories = set(factory_qnames_in_module("tpy"))
1183
+ expected_tpy = {qn for qn in FACTORY_SNAPSHOT if qn.startswith("tpy.")}
1184
+ assert tpy_factories == expected_tpy
1185
+
1186
+
1187
+ def test_fixed_int_names_stay_in_sync():
1188
+ """Three modules carry a `_FIXED_INT_NAMES` set (one hardcoded in
1189
+ parser after F.3f.3; two derived from ALL_FIXED_INTS in codegen
1190
+ statements and macro_api). If a new fixed-int width ever lands in
1191
+ typesys.ALL_FIXED_INTS, the hardcoded copy in parser must update in
1192
+ lockstep; this test pins the invariant so a single addition surfaces
1193
+ all sites at once.
1194
+
1195
+ codegen_cpp.functions._SCALAR_ZERO_CTOR_NAMES is intentionally
1196
+ broader (adds `int`/`float`/`bool` for zero-arg scalar ctor codegen)
1197
+ and is not covered here.
1198
+ """
1199
+ expected = frozenset(str(t) for t in ALL_FIXED_INTS)
1200
+ assert PARSER_FIXED_INT_NAMES == expected, (
1201
+ f"parser._FIXED_INT_NAMES drifted from ALL_FIXED_INTS: "
1202
+ f"missing={expected - PARSER_FIXED_INT_NAMES}, "
1203
+ f"extra={PARSER_FIXED_INT_NAMES - expected}"
1204
+ )
1205
+ assert StatementGenerator._FIXED_INT_NAMES == expected
1206
+ assert MACRO_FIXED_INT_NAMES == expected
1207
+
1208
+
1209
+ # =========================================================================
1210
+ # Protocol snapshot (TypeRegistry.protocols retirement).
1211
+ #
1212
+ # Hand-written golden table of ProtocolInfo fields for every stdlib
1213
+ # protocol the compiler registers when `typing` + `tpy` are imported.
1214
+ # The retirement of the short-name `TypeRegistry.protocols` dict in
1215
+ # favor of qname-keyed `TypeDef.protocol` is an invariance refactor:
1216
+ # every qname below must still resolve to the same ProtocolInfo
1217
+ # payload after each migration step (flipping callers to
1218
+ # `protocol_info_of`, reshaping storage, deleting the legacy dict).
1219
+ #
1220
+ # Mirrors PRIMITIVE_SNAPSHOT (primitive migration) and ENUM_SNAPSHOT
1221
+ # (enum migration). Hand-maintained -- regenerating from the registry
1222
+ # would defeat the check.
1223
+ # =========================================================================
1224
+
1225
+
1226
+ PROTOCOL_SNAPSHOT: dict[str, dict] = {
1227
+ # typing.* protocols (tpy._typing, collapsed via cpp_namespace="tpystd::typing")
1228
+ "typing.Sized": dict(
1229
+ name="Sized", module="typing",
1230
+ is_dynamic=False, is_marker=False, is_readonly=False,
1231
+ cpp_concept=None,
1232
+ type_params=(), parent_protocols=(),
1233
+ methods=("__len__",),
1234
+ ),
1235
+ "typing.Sequence": dict(
1236
+ name="Sequence", module="typing",
1237
+ is_dynamic=False, is_marker=False, is_readonly=False,
1238
+ cpp_concept=None,
1239
+ type_params=("T",), parent_protocols=(),
1240
+ methods=("__getitem__", "__len__"),
1241
+ ),
1242
+ "typing.MutableSequence": dict(
1243
+ name="MutableSequence", module="typing",
1244
+ is_dynamic=False, is_marker=False, is_readonly=False,
1245
+ cpp_concept=None,
1246
+ type_params=("T",), parent_protocols=(),
1247
+ methods=("__getitem__", "__len__", "__setitem__"),
1248
+ ),
1249
+ "typing.Iterator": dict(
1250
+ name="Iterator", module="typing",
1251
+ is_dynamic=False, is_marker=False, is_readonly=False,
1252
+ cpp_concept=None,
1253
+ type_params=("T",), parent_protocols=(),
1254
+ methods=("__iter__", "__next__"),
1255
+ ),
1256
+ "typing.Iterable": dict(
1257
+ name="Iterable", module="typing",
1258
+ is_dynamic=False, is_marker=False, is_readonly=False,
1259
+ cpp_concept=None,
1260
+ type_params=("T",), parent_protocols=(),
1261
+ methods=("__iter__",),
1262
+ ),
1263
+ # tpy.* @dynamic protocols (tpy._core._types)
1264
+ "tpy.Throwable": dict(
1265
+ name="Throwable", module="tpy",
1266
+ is_dynamic=True, is_marker=False, is_readonly=False,
1267
+ cpp_concept="::tpy::Throwable",
1268
+ type_params=(), parent_protocols=(),
1269
+ methods=("__raise__", "clone"),
1270
+ ),
1271
+ # tpy.* structural protocols (tpy._core._types)
1272
+ "tpy.coro.Awaitable": dict(
1273
+ name="Awaitable", module="tpy.coro",
1274
+ is_dynamic=False, is_marker=False, is_readonly=False,
1275
+ cpp_concept=None,
1276
+ type_params=("T",), parent_protocols=(),
1277
+ methods=("__poll__",),
1278
+ ),
1279
+ "tpy.coro.Awaker": dict(
1280
+ name="Awaker", module="tpy.coro",
1281
+ is_dynamic=True, is_marker=False, is_readonly=False,
1282
+ cpp_concept=None,
1283
+ type_params=(), parent_protocols=(),
1284
+ methods=("mark_runnable",),
1285
+ ),
1286
+ "tpy.coro.Cancellable": dict(
1287
+ name="Cancellable", module="tpy.coro",
1288
+ is_dynamic=True, is_marker=False, is_readonly=False,
1289
+ cpp_concept=None,
1290
+ type_params=("T",), parent_protocols=(),
1291
+ methods=("__poll__", "cancel"),
1292
+ ),
1293
+ "tpy.Truthy": dict(
1294
+ name="Truthy", module="tpy",
1295
+ is_dynamic=False, is_marker=False, is_readonly=False,
1296
+ cpp_concept=None,
1297
+ type_params=(), parent_protocols=(),
1298
+ methods=("__bool__",),
1299
+ ),
1300
+ "tpy.Stringable": dict(
1301
+ name="Stringable", module="tpy",
1302
+ is_dynamic=False, is_marker=False, is_readonly=False,
1303
+ cpp_concept=None,
1304
+ type_params=(), parent_protocols=(),
1305
+ methods=("__str__",),
1306
+ ),
1307
+ "tpy.Representable": dict(
1308
+ name="Representable", module="tpy",
1309
+ is_dynamic=False, is_marker=False, is_readonly=False,
1310
+ cpp_concept=None,
1311
+ type_params=(), parent_protocols=(),
1312
+ methods=("__repr__",),
1313
+ ),
1314
+ "tpy.Hashable": dict(
1315
+ name="Hashable", module="tpy",
1316
+ is_dynamic=False, is_marker=False, is_readonly=False,
1317
+ cpp_concept=None,
1318
+ type_params=(), parent_protocols=(),
1319
+ methods=("__hash__",),
1320
+ ),
1321
+ "tpy.Comparable": dict(
1322
+ name="Comparable", module="tpy",
1323
+ is_dynamic=False, is_marker=False, is_readonly=False,
1324
+ cpp_concept=None,
1325
+ type_params=(), parent_protocols=(),
1326
+ methods=("__lt__",),
1327
+ ),
1328
+ "tpy.Equatable": dict(
1329
+ name="Equatable", module="tpy",
1330
+ is_dynamic=False, is_marker=False, is_readonly=False,
1331
+ cpp_concept=None,
1332
+ type_params=(), parent_protocols=(),
1333
+ methods=("__eq__",),
1334
+ ),
1335
+ "tpy.Deref": dict(
1336
+ name="Deref", module="tpy",
1337
+ is_dynamic=False, is_marker=False, is_readonly=False,
1338
+ cpp_concept=None,
1339
+ type_params=("T",), parent_protocols=(),
1340
+ methods=("__deref__",),
1341
+ ),
1342
+ "tpy.Spannable": dict(
1343
+ name="Spannable", module="tpy",
1344
+ is_dynamic=False, is_marker=False, is_readonly=False,
1345
+ cpp_concept=None,
1346
+ type_params=("T",), parent_protocols=(),
1347
+ methods=("__span__",),
1348
+ ),
1349
+ "tpy.Writable": dict(
1350
+ name="Writable", module="tpy",
1351
+ is_dynamic=False, is_marker=False, is_readonly=False,
1352
+ cpp_concept=None,
1353
+ type_params=(), parent_protocols=(),
1354
+ methods=("flush", "write"),
1355
+ ),
1356
+ "tpy.Readable": dict(
1357
+ name="Readable", module="tpy",
1358
+ is_dynamic=False, is_marker=False, is_readonly=False,
1359
+ cpp_concept=None,
1360
+ type_params=(), parent_protocols=(),
1361
+ methods=("read", "readline"),
1362
+ ),
1363
+ "tpy.BinaryWritable": dict(
1364
+ name="BinaryWritable", module="tpy",
1365
+ is_dynamic=False, is_marker=False, is_readonly=False,
1366
+ cpp_concept=None,
1367
+ type_params=(), parent_protocols=(),
1368
+ methods=("flush", "write"),
1369
+ ),
1370
+ "tpy.BinaryReadable": dict(
1371
+ name="BinaryReadable", module="tpy",
1372
+ is_dynamic=False, is_marker=False, is_readonly=False,
1373
+ cpp_concept=None,
1374
+ type_params=(), parent_protocols=(),
1375
+ methods=("read", "readline"),
1376
+ ),
1377
+ "tpy.Seekable": dict(
1378
+ name="Seekable", module="tpy",
1379
+ is_dynamic=False, is_marker=False, is_readonly=False,
1380
+ cpp_concept=None,
1381
+ type_params=(), parent_protocols=(),
1382
+ methods=("seek", "tell"),
1383
+ ),
1384
+ "tpy.Closable": dict(
1385
+ name="Closable", module="tpy",
1386
+ is_dynamic=False, is_marker=False, is_readonly=False,
1387
+ cpp_concept=None,
1388
+ type_params=(), parent_protocols=(),
1389
+ methods=("close",),
1390
+ ),
1391
+ # tpy.* @native marker / concept-backed protocols
1392
+ "tpy.NativeIterable": dict(
1393
+ name="NativeIterable", module="tpy",
1394
+ is_dynamic=False, is_marker=True, is_readonly=False,
1395
+ cpp_concept="::tpy::NativeIterable",
1396
+ type_params=("T",),
1397
+ parent_protocols=(
1398
+ ts.NominalType(
1399
+ name="Iterable",
1400
+ type_args=(ts.TypeParamRef(
1401
+ name="T", bound=None, kind=ts.TypeParamKind.TYPE,
1402
+ ),),
1403
+ is_protocol=True,
1404
+ _module_qname="typing.Iterable",
1405
+ ),
1406
+ ),
1407
+ methods=(),
1408
+ ),
1409
+ "tpy.NativeRangeConstructible": dict(
1410
+ name="NativeRangeConstructible", module="tpy",
1411
+ is_dynamic=False, is_marker=True, is_readonly=False,
1412
+ cpp_concept="::tpy::NativeRangeConstructible",
1413
+ type_params=("T",), parent_protocols=(),
1414
+ methods=(),
1415
+ ),
1416
+ "tpy.ValueType": dict(
1417
+ name="ValueType", module="tpy",
1418
+ is_dynamic=False, is_marker=True, is_readonly=False,
1419
+ cpp_concept="::tpy::ValueType",
1420
+ type_params=(), parent_protocols=(),
1421
+ methods=(),
1422
+ ),
1423
+ "tpy.Copyable": dict(
1424
+ name="Copyable", module="tpy",
1425
+ is_dynamic=False, is_marker=True, is_readonly=False,
1426
+ cpp_concept="::tpy::Copyable",
1427
+ type_params=(), parent_protocols=(),
1428
+ methods=(),
1429
+ ),
1430
+ "tpy.Send": dict(
1431
+ name="Send", module="tpy",
1432
+ is_dynamic=False, is_marker=True, is_readonly=False,
1433
+ cpp_concept="::tpy::Send",
1434
+ type_params=(), parent_protocols=(),
1435
+ methods=(),
1436
+ ),
1437
+ "tpy.Sync": dict(
1438
+ name="Sync", module="tpy",
1439
+ is_dynamic=False, is_marker=True, is_readonly=False,
1440
+ cpp_concept="::tpy::Sync",
1441
+ type_params=(), parent_protocols=(),
1442
+ methods=(),
1443
+ ),
1444
+ "tpy.Default": dict(
1445
+ name="Default", module="tpy",
1446
+ is_dynamic=False, is_marker=True, is_readonly=False,
1447
+ cpp_concept="::std::default_initializable",
1448
+ type_params=(), parent_protocols=(),
1449
+ methods=(),
1450
+ ),
1451
+ "tpy.ReturnException": dict(
1452
+ name="ReturnException", module="tpy",
1453
+ is_dynamic=False, is_marker=True, is_readonly=False,
1454
+ cpp_concept="::tpy::ReturnException",
1455
+ type_params=(), parent_protocols=(),
1456
+ methods=(),
1457
+ ),
1458
+ "tpy.Covariant": dict(
1459
+ name="Covariant", module="tpy",
1460
+ is_dynamic=False, is_marker=True, is_readonly=False,
1461
+ cpp_concept="::tpy::Covariant",
1462
+ type_params=("T",), parent_protocols=(),
1463
+ methods=(),
1464
+ ),
1465
+ "tpy.AnyFixedInt": dict(
1466
+ name="AnyFixedInt", module="tpy",
1467
+ is_dynamic=False, is_marker=True, is_readonly=False,
1468
+ cpp_concept="::tpy::AnyFixedInt",
1469
+ type_params=(), parent_protocols=(),
1470
+ methods=(),
1471
+ ),
1472
+ "tpy.AnyFixedSigned": dict(
1473
+ name="AnyFixedSigned", module="tpy",
1474
+ is_dynamic=False, is_marker=True, is_readonly=False,
1475
+ cpp_concept="::tpy::AnyFixedSigned",
1476
+ type_params=(), parent_protocols=(),
1477
+ methods=(),
1478
+ ),
1479
+ "tpy.AnyFixedUnsigned": dict(
1480
+ name="AnyFixedUnsigned", module="tpy",
1481
+ is_dynamic=False, is_marker=True, is_readonly=False,
1482
+ cpp_concept="::tpy::AnyFixedUnsigned",
1483
+ type_params=(), parent_protocols=(),
1484
+ methods=(),
1485
+ ),
1486
+ }
1487
+
1488
+
1489
+ _PROTOCOL_SNAPSHOT_SOURCE = """\
1490
+ from typing import Sized, Iterable, Iterator, Sequence, MutableSequence
1491
+ from tpy import (
1492
+ Truthy, Stringable, Representable, Hashable, Comparable, Equatable,
1493
+ Deref, Spannable, Writable, Readable, BinaryWritable, BinaryReadable,
1494
+ Seekable, Closable,
1495
+ NativeIterable, NativeRangeConstructible, ValueType, Send, Sync,
1496
+ Default, ReturnException, Covariant,
1497
+ AnyFixedInt, AnyFixedSigned, AnyFixedUnsigned,
1498
+ )
1499
+ from tpy.coro import Awaitable, Cancellable
1500
+ """
1501
+
1502
+
1503
+ @pytest.fixture(scope="module")
1504
+ def _protocol_snapshot_compiled():
1505
+ """Compile a source that imports every protocol in PROTOCOL_SNAPSHOT.
1506
+
1507
+ Scoped to the module so the compile runs once; the conftest autouse
1508
+ fixture clears dynamic state around each test, so individual tests
1509
+ that need the populated registry recompile via this fixture.
1510
+ """
1511
+ from tpyc import get_lib_dir
1512
+ from tpyc.compiler import Compiler
1513
+ compiler = Compiler.from_source(
1514
+ _PROTOCOL_SNAPSHOT_SOURCE, lib_dirs=[get_lib_dir() / "tpy"]
1515
+ )
1516
+ compiler.compile()
1517
+ return compiler
1518
+
1519
+
1520
+ @pytest.fixture
1521
+ def _protocol_registry(_protocol_snapshot_compiled):
1522
+ """Per-test fixture: recompile the snapshot source so the TypeDef
1523
+ registry is populated inside the test's `clear_all_compilation_state`
1524
+ window. The module-scoped fixture above is retained for any future
1525
+ callers that legitimately want the pre-clear state.
1526
+
1527
+ The fixture keeps the recompiled Compiler active for the test body
1528
+ so reads of `compiler.dynamic_type_defs` (via `get_type_def` etc.)
1529
+ see this compilation's slice, not the conftest fake context's empty
1530
+ one.
1531
+ """
1532
+ from tpyc import get_lib_dir
1533
+ from tpyc.compiler import Compiler
1534
+ compiler = Compiler.from_source(
1535
+ _PROTOCOL_SNAPSHOT_SOURCE, lib_dirs=[get_lib_dir() / "tpy"]
1536
+ )
1537
+ compiler.compile()
1538
+ with activate_compiler(compiler):
1539
+ yield compiler
1540
+
1541
+
1542
+ def test_protocol_snapshot_covers_every_compiled_protocol(_protocol_registry):
1543
+ """Every PROTOCOL-category TypeDef the snapshot compile populates must
1544
+ have a PROTOCOL_SNAPSHOT entry (and vice versa). Guards against silent
1545
+ drift: a new stdlib protocol without a snapshot entry, or a snapshot
1546
+ entry pointing at a qname the registration path no longer creates."""
1547
+ # PROTOCOL TypeDefs live in two places: pre-existing static entries
1548
+ # whose payload was attached during compile (mostly @builtin_type
1549
+ # stub protocols), and purely-dynamic entries on the active
1550
+ # compiler's `dynamic_type_defs`. Both contribute.
1551
+ live = {
1552
+ qn for qn, td in _type_defs.items()
1553
+ if td.category is TypeCategory.PROTOCOL and td.protocol is not None
1554
+ }
1555
+ live |= {
1556
+ qn for qn, td in _protocol_registry.dynamic_type_defs.items()
1557
+ if td.category is TypeCategory.PROTOCOL and td.protocol is not None
1558
+ }
1559
+ missing = live - set(PROTOCOL_SNAPSHOT)
1560
+ assert not missing, (
1561
+ f"Protocol qnames registered by the snapshot compile without a "
1562
+ f"PROTOCOL_SNAPSHOT entry: {sorted(missing)}. Add them with "
1563
+ f"their expected ProtocolInfo fields."
1564
+ )
1565
+ extra = set(PROTOCOL_SNAPSHOT) - live
1566
+ assert not extra, (
1567
+ f"PROTOCOL_SNAPSHOT entries that the compile didn't register: "
1568
+ f"{sorted(extra)}. Either the registration path regressed or the "
1569
+ f"snapshot names a qname that no longer exists."
1570
+ )
1571
+
1572
+
1573
+ @pytest.mark.parametrize("qname", sorted(PROTOCOL_SNAPSHOT))
1574
+ def test_protocol_type_def_matches_snapshot(qname, _protocol_registry):
1575
+ """Hand-coded expected values for every stdlib protocol survive the
1576
+ retirement of TypeRegistry.protocols. Reading goes through
1577
+ `TypeDef.protocol` (qname-keyed), which is authoritative; after each
1578
+ migration step, every field below must still match."""
1579
+ td = get_type_def(qname)
1580
+ assert td is not None, f"No TypeDef for {qname}"
1581
+ assert td.category is TypeCategory.PROTOCOL, (
1582
+ f"{qname}: TypeDef.category={td.category}, expected PROTOCOL"
1583
+ )
1584
+ info = td.protocol
1585
+ assert info is not None, f"{qname}: TypeDef.protocol payload missing"
1586
+ expected = PROTOCOL_SNAPSHOT[qname]
1587
+
1588
+ assert info.name == expected["name"]
1589
+ assert info.module == expected["module"]
1590
+ assert info.is_dynamic == expected["is_dynamic"]
1591
+ assert info.is_marker == expected["is_marker"]
1592
+ assert info.is_readonly == expected["is_readonly"]
1593
+ assert info.cpp_concept == expected["cpp_concept"]
1594
+ assert tuple(info.type_params) == expected["type_params"]
1595
+ assert tuple(info.parent_protocols) == expected["parent_protocols"]
1596
+ assert tuple(sorted(m.name for m in info.methods)) == expected["methods"]
1597
+
1598
+
1599
+ @pytest.mark.parametrize("qname", sorted(PROTOCOL_SNAPSHOT))
1600
+ def test_protocol_info_of_matches_type_def(qname, _protocol_registry):
1601
+ """`protocol_info_of(NominalType(qname))` must return the same
1602
+ ProtocolInfo instance as the direct `TypeDef.protocol` lookup. This
1603
+ pins the migration target: all callers eventually route through
1604
+ `protocol_info_of` and must see identical data."""
1605
+ from tpyc.type_def_registry import protocol_info_of
1606
+ td = get_type_def(qname)
1607
+ assert td is not None and td.protocol is not None
1608
+ # Synthesize a NominalType carrying the qname; protocol_info_of goes
1609
+ # through `type_def_of(t)` which keys on `qualified_name()`.
1610
+ module, _, short = qname.rpartition(".")
1611
+ inst = ts.NominalType(
1612
+ name=short, type_args=(), is_protocol=True, _module_qname=qname,
1613
+ )
1614
+ info = protocol_info_of(inst)
1615
+ assert info is td.protocol, (
1616
+ f"{qname}: protocol_info_of returned {info!r}, TypeDef.protocol={td.protocol!r}"
1617
+ )
1618
+
1619
+
1620
+ def test_scan_by_short_name_honors_import_alias():
1621
+ """`register_protocol(info, local_name)` must make `scan_by_short_name`
1622
+ resolve both the protocol's canonical short name and any local import
1623
+ alias to the same `ProtocolInfo`.
1624
+
1625
+ The alias path is the load-bearing reason the short-name
1626
+ `_protocols_by_local_name` table exists at all; without it the
1627
+ short-name helper could be replaced by a scan over qnames. Pin the
1628
+ semantics here so the helper's contract doesn't silently regress when
1629
+ the storage layer evolves further (e.g. a future move to TypeDef
1630
+ discovery).
1631
+
1632
+ Uses a synthetic ProtocolInfo rather than a real compile so the test
1633
+ stays fast and doesn't depend on the multi-module import plumbing.
1634
+ """
1635
+ registry = ts.TypeRegistry()
1636
+ info = ts.ProtocolInfo(
1637
+ name="Sized",
1638
+ methods=[],
1639
+ module="typing",
1640
+ )
1641
+ registry.register_protocol(info, "MySized")
1642
+
1643
+ # Alias resolves to the original ProtocolInfo.
1644
+ assert registry.scan_by_short_name("MySized") is info
1645
+
1646
+ # Canonical name is NOT auto-aliased when a local name is provided;
1647
+ # only the explicit binding is recorded.
1648
+ assert registry.scan_by_short_name("Sized") is None
1649
+
1650
+ # A second registration without a local-name override exposes the
1651
+ # canonical short name too -- mimicking the typical stdlib-import
1652
+ # path in `compiler.py::_analyze_module`.
1653
+ registry.register_protocol(info)
1654
+ assert registry.scan_by_short_name("Sized") is info
1655
+ assert registry.scan_by_short_name("MySized") is info # alias still works
1656
+
1657
+ # Unknown name returns None, not a random collision.
1658
+ assert registry.scan_by_short_name("Nonexistent") is None
1659
+
1660
+
1661
+ def test_resolve_type_for_codegen_does_not_promote_record_to_protocol():
1662
+ """`ProtocolGenerator.resolve_type_for_codegen` probes `protocol_info_of`
1663
+ with `is_protocol=True` set on the NominalType. When `_module_qname`
1664
+ points at a RECORD-category TypeDef (short name happens to collide with
1665
+ a protocol in another module), the probe must return None and the
1666
+ type must stay classified as a record.
1667
+
1668
+ The pre-P.1 path used a short-name `registry.get_protocol(typ.name)`
1669
+ lookup that would have matched the collision and silently promoted
1670
+ the record. Pinning the qname-driven behavior here prevents a
1671
+ regression to the short-name semantics during any future reshape of
1672
+ `resolve_type_for_codegen`.
1673
+ """
1674
+ from tpyc.type_def_registry import (
1675
+ attach_dynamic_type_def, TypeCategory,
1676
+ )
1677
+ # A user record at `user_mod.Widget`.
1678
+ record_qname = "user_mod.Widget"
1679
+ record_info = ts.RecordInfo(
1680
+ name="Widget",
1681
+ module="user_mod",
1682
+ fields=[],
1683
+ methods={},
1684
+ )
1685
+ attach_dynamic_type_def(record_qname, TypeCategory.RECORD, record=record_info)
1686
+
1687
+ # An unrelated protocol also named `Widget` in a different module --
1688
+ # this would have short-name-matched under the old lookup.
1689
+ protocol_qname = "other_mod.Widget"
1690
+ protocol_info = ts.ProtocolInfo(
1691
+ name="Widget", methods=[], module="other_mod",
1692
+ )
1693
+ attach_dynamic_type_def(protocol_qname, TypeCategory.PROTOCOL,
1694
+ protocol=protocol_info)
1695
+
1696
+ # NominalType bound to the record qname, not flagged as protocol.
1697
+ record_nominal = ts.NominalType(
1698
+ name="Widget", type_args=(), is_protocol=False,
1699
+ _module_qname=record_qname,
1700
+ )
1701
+
1702
+ # Build a minimal ProtocolGenerator with just enough context to call
1703
+ # resolve_type_for_codegen; the method only reaches into
1704
+ # `protocol_info_of`, not into `ctx.analyzer.*`.
1705
+ from tpyc.codegen_cpp.protocols import ProtocolGenerator
1706
+
1707
+ class _StubCtx:
1708
+ pass
1709
+
1710
+ pg = ProtocolGenerator(_StubCtx())
1711
+ resolved = pg.resolve_type_for_codegen(record_nominal)
1712
+
1713
+ # Record must NOT be flipped to a protocol.
1714
+ assert resolved is record_nominal, (
1715
+ "resolve_type_for_codegen must leave a record-qname NominalType "
1716
+ "unchanged even when a protocol of the same short name exists "
1717
+ f"elsewhere; got {resolved!r}"
1718
+ )
1719
+ assert resolved.is_protocol is False
1720
+
1721
+ # Sanity: a placeholder NominalType (no _module_qname) whose short
1722
+ # name resolves to the `other_mod` protocol via `_protocol_modules`
1723
+ # *should* get flipped -- this is the legitimate promotion case.
1724
+ from tpyc.typesys import register_protocol_module
1725
+ register_protocol_module("Widget", "other_mod")
1726
+ placeholder = ts.NominalType(name="Widget", type_args=(), is_protocol=False)
1727
+ promoted = pg.resolve_type_for_codegen(placeholder)
1728
+ assert promoted.is_protocol is True
1729
+ assert promoted.qualified_name() == protocol_qname