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,2258 @@
1
+ """
2
+ TurboPython C++ Code Generator
3
+
4
+ Main orchestrator for generating C++ code from TurboPython AST.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from dataclasses import dataclass, field
9
+ from typing import Callable, TextIO, TYPE_CHECKING
10
+ import io
11
+ import sys as _sys
12
+
13
+ from ..typesys import TpyType, NominalType, UnionType, OwnType, PendingListType, PtrType, NoneType, VoidType, BIGINT, RecordInfo, ProtocolInfo, clear_codegen_state, register_native_cpp_name, register_union_alias, resolve_int_literals, is_void_like_type, bare_name
14
+ from ..compilation_context import require_current_compiler
15
+ from ..type_def_registry import type_def_of, is_enum_type, enum_info_of, protocol_info_of
16
+ from ..parse import TpyModule, TpyRecord, TpyFunction, TpyVarDecl, VarLinkage
17
+ from ..parse.nodes import TpyTupleUnpack, ModuleDirectives, TpyTry, TpyWith
18
+ from .resumable_cfg import (
19
+ ResumableShape, resumable_state,
20
+ )
21
+
22
+ from .context import CodeGenContext, CodeGenError, CodeGenOptions, module_to_cpp_namespace, module_has_cpp_namespace_override, qualified_cpp_name, qualify_native_name, escape_cpp_string, escape_cpp_char, escape_cpp_name
23
+ from .types import TypeResolver
24
+ from .protocols import ProtocolGenerator
25
+ from .builtins import BuiltinGenerator
26
+ from .expressions import ExpressionGenerator
27
+ from .statements import StatementGenerator
28
+ from .records import RecordGenerator
29
+ from .functions import FunctionGenerator
30
+ from .type_resolution import resolve_stmt_type_cascade
31
+ from .string_dispatch import find_best_discriminator, STRING_SWITCH_THRESHOLD
32
+ from ..symbol_binding import SymbolKind
33
+
34
+ if TYPE_CHECKING:
35
+ from ..sema import SemanticAnalyzer
36
+
37
+
38
+ def _emits_own_cpp(typ: NominalType) -> bool:
39
+ """True if this NominalType's C++ emission bypasses its Python name --
40
+ either via a TypeDef cpp_formatter (builtins like list, str), via a
41
+ native_name mapping (@native records), or because it has no runtime
42
+ form (compile-time-only types like FStr). In those cases, an imported
43
+ `using PythonName = ...;` alias is dead code: the Python name never
44
+ appears in generated C++."""
45
+ td = type_def_of(typ)
46
+ if td is not None and (td.cpp_formatter is not None or td.is_compile_time_only):
47
+ return True
48
+ return typ.name in require_current_compiler().native_cpp_names
49
+
50
+ # Maps user-facing platform names to sys.platform prefixes (also in compiler.py)
51
+ _PLATFORM_MAP = {"windows": "win32", "linux": "linux", "macos": "darwin"}
52
+
53
+
54
+ def _platform_matches(platform_filter: str | None) -> bool:
55
+ """Check if a platform filter matches the current platform."""
56
+ if platform_filter is None:
57
+ return True
58
+ mapped = _PLATFORM_MAP.get(platform_filter.lower(), platform_filter)
59
+ return _sys.platform.startswith(mapped)
60
+
61
+
62
+ @dataclass
63
+ class _ProtocolDeps:
64
+ """Dependency sets computed for protocol ordering."""
65
+ protocol_referenced_records: set[str] = field(default_factory=set)
66
+ bound_protocols: set[str] = field(default_factory=set)
67
+ bound_protocol_records: set[str] = field(default_factory=set)
68
+ early_definition_records: set[str] = field(default_factory=set)
69
+ prereq_protocols: set[str] = field(default_factory=set)
70
+ prereq_protocol_records: set[str] = field(default_factory=set)
71
+ module_record_names: set[str] = field(default_factory=set)
72
+ records_by_name: dict[str, TpyRecord] = field(default_factory=dict)
73
+
74
+
75
+ def _references_nested_type(typ: TpyType) -> bool:
76
+ """Check if a type references a nested type (dotted name)."""
77
+ if (isinstance(typ, NominalType) or is_enum_type(typ)) and "." in typ.name:
78
+ return True
79
+ return any(_references_nested_type(inner) for inner in typ.inner_types())
80
+
81
+
82
+ def _func_uses_nested_type(func: TpyFunction) -> bool:
83
+ """Check if a function's signature references any nested type."""
84
+ for _, ptype in func.params:
85
+ if _references_nested_type(ptype):
86
+ return True
87
+ return _references_nested_type(func.return_type)
88
+
89
+
90
+ class CodeGenerator:
91
+ """Generates C++ code from TurboPython AST."""
92
+
93
+ def __init__(self, analyzer: SemanticAnalyzer, options: CodeGenOptions | None = None):
94
+ self.analyzer = analyzer
95
+ self.options = options or CodeGenOptions()
96
+
97
+ # Create context (shared state)
98
+ self.ctx = CodeGenContext(
99
+ analyzer=analyzer,
100
+ options=self.options,
101
+ )
102
+
103
+ # Create component generators (ordered by dependencies)
104
+ self.protocols = ProtocolGenerator(self.ctx)
105
+ self.types = TypeResolver(self.ctx, self.protocols)
106
+ self.builtins = BuiltinGenerator(self.ctx, self.types)
107
+ self.expressions = ExpressionGenerator(self.ctx, self.types, self.builtins, self.protocols)
108
+ self.statements = StatementGenerator(self.ctx, self.types, self.builtins, self.protocols, self.expressions)
109
+ self.functions = FunctionGenerator(self.ctx, self.types, self.protocols, self.statements)
110
+ self.records = RecordGenerator(self.ctx, self.types, self.protocols, self.expressions, self.functions)
111
+
112
+ # Generator codegen (must be created after wiring since it uses statements/functions)
113
+ from .gen_generators import GeneratorCodegen
114
+ self.gen_generators = GeneratorCodegen(
115
+ self.ctx, self.types, self.expressions, self.statements, self.functions)
116
+ self.records.gen_generators = self.gen_generators
117
+
118
+ # Async coroutine codegen (state-machine struct + Poll<T> poll())
119
+ from .gen_async import AsyncCoroCodegen
120
+ self.gen_async = AsyncCoroCodegen(
121
+ self.ctx, self.types, self.expressions, self.statements, self.functions)
122
+ self.records.gen_async = self.gen_async
123
+ # The resumable for-loop emit reuses the legacy strategy analysis
124
+ # (`_analyze_for_strategy`) so range/begin_end peepholes match.
125
+ self.gen_async.gen_generators = self.gen_generators
126
+
127
+ def generate(self, module: TpyModule, module_name: str = "generated",
128
+ is_entry_point: bool = True,
129
+ actual_user_modules: set[str] | None = None,
130
+ implicit_stdlib_modules: set[str] | None = None,
131
+ cycle_peers: 'frozenset[str] | None' = None) -> tuple[str, str]:
132
+ """Generate C++ header and source files.
133
+
134
+ Args:
135
+ module: The parsed TurboPython module AST.
136
+ module_name: Name for the generated files (used in #include).
137
+ is_entry_point: True if this is the entry point module (generates main()).
138
+ actual_user_modules: Set of module names that are actually user modules (have source files).
139
+ If None, uses module.user_module_imports (legacy behavior).
140
+ """
141
+ self.ctx.module_name = module_name
142
+ self.ctx.source_lines = module.source_lines
143
+ self.ctx.cycle_peers = cycle_peers or frozenset()
144
+ # Filter out keyword stubs (@builtin_type classes / @builtin_decorator functions
145
+ # that exist only for import resolution -- no C++ code needed).
146
+ module.records = [
147
+ r for r in module.records
148
+ if not (ri := self.analyzer.registry.get_record(r.name)) or not ri.is_keyword_stub
149
+ ]
150
+ module.functions = [
151
+ f for f in module.functions if not f.builtin_decorator_key
152
+ ]
153
+ # Populate native C++ name mappings for this module's codegen.
154
+ # Must include both own records and imported records so NominalType.to_cpp()
155
+ # resolves correctly in all type positions (Ptr[Rect] -> SDL_Rect*, etc.)
156
+ clear_codegen_state()
157
+ native_cpp_names = require_current_compiler().native_cpp_names
158
+ for record in module.records:
159
+ record_info = self.analyzer.registry.get_record(record.name)
160
+ if record_info and record_info.is_native and record_info.native_name:
161
+ register_native_cpp_name(record.name, record_info.native_name)
162
+ # @native enums: register their canonical C++ qname so type-position
163
+ # renderings (`Optional[E]`, `list[E]`, function signatures) resolve
164
+ # to `ns::E` instead of `tpyapp::<module>::E`.
165
+ for enum in module.enums:
166
+ if not enum.is_native:
167
+ continue
168
+ enum_type = self.analyzer.registry.get_enum(enum.name)
169
+ if enum_type is None:
170
+ continue
171
+ einfo = enum_info_of(enum_type)
172
+ if einfo is not None and einfo.native_name:
173
+ register_native_cpp_name(enum.name, einfo.native_name)
174
+ # Register nested type names: "Outer.Inner" -> "Outer::Inner" for C++ qualified access.
175
+ # Write the map directly to avoid ensure_qualified adding "::" prefix
176
+ # (these are module-local types, not cross-module references).
177
+ for record in module.all_records():
178
+ if "." in record.name:
179
+ native_cpp_names[record.name] = record.name.replace(".", "::")
180
+ for enum in module.all_enums():
181
+ if "." in enum.name:
182
+ native_cpp_names[enum.name] = enum.name.replace(".", "::")
183
+ # Register C++ names for imported records/enums using their canonical
184
+ # (declaring-module) qname. NominalType.to_cpp() consults these when it
185
+ # has no qname-based formatter on hand. Iterate the full record/enum
186
+ # registries; imported_*_qualification filters out locals and builtins.
187
+ current_module = self.analyzer.ctx.module_name
188
+ for local_name, record_info in self.analyzer.registry.records.items():
189
+ if record_info.is_native and record_info.native_name:
190
+ register_native_cpp_name(local_name, record_info.native_name)
191
+ continue
192
+ if record_info.is_native:
193
+ continue
194
+ qual = self.analyzer.registry.imported_record_qualification(
195
+ local_name, current_module)
196
+ if qual is not None:
197
+ register_native_cpp_name(local_name, qualified_cpp_name(*qual))
198
+ # Cross-module @dynamic protocols (e.g. `Awaker` imported into
199
+ # `asyncio._executor` from `tpy.coro`) need the same `native_cpp_names`
200
+ # qualification as user records: `NominalType.to_cpp()` consults the
201
+ # map for protocol short names too. Locally-defined @dynamic protocols
202
+ # skip this registration so same-module references emit the bare name.
203
+ # Static protocols are excluded -- they monomorphize and never surface
204
+ # as a runtime C++ type.
205
+ for local_name, proto_info in self.analyzer.registry._protocols_by_local_name.items():
206
+ # @native + @dynamic protocols (e.g. `Throwable` which lives in
207
+ # runtime/cpp/include/tpy/throwable.hpp as ::tpy::Throwable) get
208
+ # their cpp_concept name as the C++ rendering, both for the
209
+ # defining module and for any module that imports them. The
210
+ # imported_protocol_qualification path would otherwise route
211
+ # through the codegen-emitted namespace (`tpystd::tpy::Throwable`)
212
+ # which doesn't exist for @native+@dynamic protocols.
213
+ if proto_info is not None and proto_info.cpp_concept and proto_info.is_dynamic:
214
+ register_native_cpp_name(local_name, proto_info.cpp_concept)
215
+ continue
216
+ qual = self.analyzer.registry.imported_protocol_qualification(
217
+ local_name, current_module)
218
+ if qual is not None:
219
+ register_native_cpp_name(local_name, qualified_cpp_name(*qual))
220
+ # `submod.X` (after `from pkg import submod`) doesn't import X by
221
+ # short name, so the loop above misses it. Walk each registered
222
+ # dep module's exports to register cross-module qualified names.
223
+ # Locally-defined record short names are excluded so a same-named
224
+ # peer record in a registered dep module never shadows the local
225
+ # `Foo`'s unqualified emission. Without this guard, a workspace
226
+ # that registers all modules into a shared dict (or any path that
227
+ # transitively pulls a peer module with a colliding short name)
228
+ # would misqualify the local record.
229
+ # Two cross-module record cases handled in one walk:
230
+ # (a) Plain user records imported from a peer module -- qualify
231
+ # through `record_info.module`.
232
+ # (b) `@builtin_type`-with-body records (Task and future
233
+ # siblings) whose stub claims a well-known qname like
234
+ # `tpy.Task` but whose body lives in a `# tpy: cpp_namespace`-
235
+ # tagged module (e.g. `asyncio._executor`). These would
236
+ # otherwise mis-qualify as `tpyapp::<module>::Name`, so the
237
+ # defining_module + cpp_namespace override path is required.
238
+ # Common skips (already registered, local shadow, @native rename)
239
+ # apply to both; the case split determines which module to qualify
240
+ # through.
241
+ local_short_record_names = {r.name for r in module.records}
242
+ for dep_module in self.analyzer.registry.modules.values():
243
+ if dep_module.name == current_module:
244
+ continue
245
+ for short, record_info in dep_module.records.items():
246
+ if short in native_cpp_names:
247
+ continue
248
+ if short in local_short_record_names:
249
+ continue
250
+ if record_info.is_native:
251
+ # @native rename handled above; skip whether or not
252
+ # native_name is set.
253
+ continue
254
+ if record_info.builtin_type_key:
255
+ # Case (b)
256
+ if record_info.is_keyword_stub:
257
+ continue
258
+ defining = record_info.defining_module
259
+ if not defining:
260
+ continue
261
+ if not module_has_cpp_namespace_override(defining):
262
+ continue
263
+ register_native_cpp_name(
264
+ short, qualified_cpp_name(defining, short))
265
+ else:
266
+ # Case (a)
267
+ if dep_module.is_builtin:
268
+ continue
269
+ if record_info.module is None or record_info.module == current_module:
270
+ continue
271
+ register_native_cpp_name(
272
+ short, qualified_cpp_name(record_info.module, short))
273
+ for local_name in list(self.analyzer.registry.enums.keys()):
274
+ qual = self.analyzer.registry.imported_enum_qualification(
275
+ local_name, current_module)
276
+ if qual is None:
277
+ continue
278
+ # @native imported enums: use their canonical C++ qname, not
279
+ # the module-qualified spelling.
280
+ enum_type = self.analyzer.registry.get_enum(local_name)
281
+ einfo = enum_info_of(enum_type) if enum_type is not None else None
282
+ if einfo is not None and einfo.is_native and einfo.native_name:
283
+ qualified = einfo.native_name
284
+ else:
285
+ qualified = qualified_cpp_name(*qual)
286
+ register_native_cpp_name(local_name, qualified)
287
+ # For aliased imports, also map the canonical name so references
288
+ # that go through NominalType.name resolve too.
289
+ original_name = qual[1]
290
+ if local_name != original_name:
291
+ register_native_cpp_name(original_name, qualified)
292
+ # Register native names from builtin type records without type_factory
293
+ # (e.g. TextIO -> tpy::TextFile, ValueError -> tpy::ValueError) so
294
+ # NominalType.to_cpp() resolves even when the type isn't explicitly imported.
295
+ # Skip names that shadow local record definitions in the current module.
296
+ local_record_names = {r.name for r in module.records}
297
+ for record_info in self.analyzer.registry.get_native_builtin_records():
298
+ if record_info.name not in local_record_names:
299
+ register_native_cpp_name(record_info.name, record_info.native_name)
300
+ # Register imported union type aliases so UnionType.to_cpp() can use
301
+ # the alias name instead of expanding to std::variant<...>. Skip
302
+ # recursive aliases here; the iter_imported_recursive_unions loop
303
+ # below registers them with their qualified C++ name so cross-module
304
+ # consumers emit the right type.
305
+ for local_name, (source_module, original_name) in self.analyzer.registry.imported_type_alias_info.items():
306
+ alias_info = self.analyzer.registry.get_type_alias_info(local_name)
307
+ if alias_info is not None and alias_info.type_params:
308
+ # Generic recursive aliases DO have a C++ identity (the wrapper
309
+ # template). Map the imported short name to the defining
310
+ # module's qualified name so RecursiveAliasInstanceType.to_cpp()
311
+ # emits `tpyapp::lib::Tree<...>` rather than a bare, undeclared
312
+ # `Tree<...>`. Generic non-recursive aliases are expanded at use
313
+ # sites and have no C++-level identity -- skip them.
314
+ src = self.analyzer.registry.modules.get(source_module)
315
+ if src is not None and original_name in src.recursive_union_names:
316
+ register_native_cpp_name(
317
+ local_name, qualified_cpp_name(source_module, original_name))
318
+ continue
319
+ alias_type = alias_info.body if alias_info is not None else None
320
+ if not isinstance(alias_type, UnionType):
321
+ continue
322
+ source_info = self.analyzer.registry.modules.get(source_module)
323
+ if source_info is not None and original_name in source_info.recursive_union_names:
324
+ continue
325
+ register_union_alias(alias_type.members, local_name)
326
+ # Filter user_module_imports to only include actual user modules (not builtins without user files)
327
+ if actual_user_modules is not None:
328
+ self.ctx.user_module_imports = {k: v for k, v in module.user_module_imports.items() if k in actual_user_modules}
329
+ self.ctx.all_user_modules = actual_user_modules
330
+ else:
331
+ self.ctx.user_module_imports = module.user_module_imports
332
+ self.ctx.all_user_modules = set(module.user_module_imports.keys())
333
+ self.ctx.implicit_stdlib_modules = implicit_stdlib_modules or set()
334
+ self.ctx.top_level_decls = dict(self.analyzer.ctx.top_level_decls)
335
+ self.ctx.macro_dep_modules = set(self.analyzer.ctx.macro_dep_modules)
336
+ # Recursive-union wrapper metadata is populated during sema
337
+ # (see analyzer._register_union_wrappers); codegen just reads
338
+ # it via UnionType.wrapper_info() / UnionType.needs_wrapper().
339
+ # Register recursive union aliases so record field rendering resolves
340
+ # e.g. Box[UnionType(Lit, BinOp)] -> Box<Expr>. Local aliases use the
341
+ # unqualified name; imported aliases are qualified to the defining
342
+ # module's namespace so cross-module consumers emit the right C++ type.
343
+ for name in module.recursive_union_names:
344
+ entry = module.type_aliases.get(name)
345
+ if entry is not None:
346
+ # Generic recursive aliases render via RecursiveAliasInstanceType
347
+ # (Tree<int>), not the member-tuple union_alias_names map; their
348
+ # members carry TypeParamRefs that must not pollute it.
349
+ if entry[2]:
350
+ continue
351
+ typ = entry[0]
352
+ if isinstance(typ, UnionType):
353
+ register_union_alias(typ.members, name)
354
+ for info in self.ctx.iter_imported_recursive_unions():
355
+ register_union_alias(
356
+ info.full_members,
357
+ qualified_cpp_name(info.origin, info.name),
358
+ )
359
+ hpp = io.StringIO()
360
+ cpp = io.StringIO()
361
+
362
+ from ..parse.nodes import FunctionLinkage
363
+ # Collect @native (C++ import) functions for out-of-namespace handling
364
+ native_funcs = [f for f in module.functions if f.linkage == FunctionLinkage.NATIVE]
365
+
366
+ # Separate global declarations from other top-level statements early
367
+ # (needed for extern declarations in header)
368
+ # Track seen names and types to handle re-declarations (z = 0; z = 5; -> one global, one assignment)
369
+ global_decls = []
370
+ final_decls: list[TpyVarDecl] = []
371
+ native_globals: list[TpyVarDecl] = []
372
+ seen_globals: dict[str, TpyType | None] = {}
373
+ for stmt in module.top_level_stmts:
374
+ if isinstance(stmt, TpyVarDecl):
375
+ if stmt.name not in seen_globals:
376
+ # Store the type for this global
377
+ var_type = resolve_stmt_type_cascade(stmt, self.analyzer, self.types)
378
+ if isinstance(var_type, OwnType):
379
+ var_type = var_type.wrapped
380
+ var_type = resolve_int_literals(var_type, self.analyzer.ctx.default_int_for_literal)
381
+ seen_globals[stmt.name] = var_type
382
+ if stmt.linkage != VarLinkage.DEFAULT:
383
+ native_globals.append(stmt)
384
+ elif stmt.is_final:
385
+ final_decls.append(stmt)
386
+ else:
387
+ global_decls.append(stmt)
388
+ elif isinstance(stmt, TpyTupleUnpack):
389
+ for i, name in enumerate(stmt.targets):
390
+ if name is None or name in seen_globals:
391
+ continue
392
+ var_type = stmt.target_types[i]
393
+ if isinstance(var_type, OwnType):
394
+ var_type = var_type.wrapped
395
+ seen_globals[name] = var_type
396
+ synthetic = TpyVarDecl(
397
+ name=name, type=var_type, init=None, loc=stmt.loc)
398
+ global_decls.append(synthetic)
399
+
400
+ # Track native global name mappings (Python name -> C/C++ name)
401
+ self.ctx.native_global_names = {
402
+ stmt.name: (stmt.native_name or stmt.name)
403
+ for stmt in native_globals
404
+ }
405
+
406
+ if module.directives.native_module:
407
+ # Native modules are declaration-only -- no .hpp or .cpp generated.
408
+ # Their # tpy: include() directives are propagated to importing modules.
409
+ return "", ""
410
+
411
+ self._write_header_preamble(hpp, native_funcs, native_globals, directives=module.directives)
412
+
413
+ self._write_source_preamble(cpp, module)
414
+
415
+ # Store global names for use in expression generation (method/field access)
416
+ self.ctx.global_names = set(seen_globals.keys())
417
+ # Track Final globals (constexpr/const at namespace scope)
418
+ self.ctx.final_globals = {stmt.name for stmt in final_decls}
419
+ # Classify globals: non-value-type -> pointer globals (T*)
420
+ # Exclude native globals and final globals
421
+ self.ctx.pointer_globals = {
422
+ name for name, typ in seen_globals.items()
423
+ if typ and not typ.is_value_type()
424
+ and not typ.needs_wrapper()
425
+ and name not in self.ctx.native_global_names
426
+ and name not in self.ctx.final_globals
427
+ }
428
+ # Also include imported non-value-type globals
429
+ for name, cell in (self.analyzer.ctx.module_attributes or {}).items():
430
+ bd = cell.binding
431
+ if bd.kind != SymbolKind.VARIABLE or bd.defining_module is None:
432
+ continue
433
+ binding = self.ctx.analyzer.global_ns.lookup_local(name)
434
+ if binding and binding.type and not binding.type.is_value_type() and not binding.type.needs_wrapper():
435
+ self.ctx.pointer_globals.add(name)
436
+ # Generate protocol ordering and forward declarations
437
+ self._generate_protocol_ordering(hpp, module, global_decls, final_decls, seen_globals)
438
+
439
+ # Generate global definitions in source (before functions)
440
+ for stmt in global_decls:
441
+ self.functions.gen_global_decl(cpp, stmt)
442
+ # Final globals: constexpr in header, const in source (BigInt only)
443
+ for stmt in final_decls:
444
+ self.functions.gen_final_global_source(cpp, stmt)
445
+ cpp.write("\n")
446
+
447
+ # Generate function definitions (skip template functions -- defined in header)
448
+ for func in module.functions:
449
+ if func.skip_codegen:
450
+ continue
451
+ if func.is_generator:
452
+ if self._resumable_generator_eligible(func):
453
+ # Generator lowered onto the resumable frame. Non-template
454
+ # generators emit the __next__ body + factory here in the
455
+ # .cpp; templated ones (generic / protocol-typed params)
456
+ # emit inline in the header instead (see the
457
+ # struct-definition pass), so skip the .cpp emission for
458
+ # them.
459
+ if not self.gen_async._is_templated_coro(func):
460
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
461
+ self.gen_async.gen_coro_poll_def(cpp, func)
462
+ cpp.write("\n")
463
+ self.gen_async.gen_coro_finally_top_def(cpp, func)
464
+ cpp.write("\n")
465
+ self.gen_async.gen_factory(cpp, func)
466
+ cpp.write("\n")
467
+ # Simple generators are defined inline in the header
468
+ continue
469
+ if func.is_async:
470
+ # Templates are emitted inline in the header (same rule as
471
+ # template functions). For non-template async coros, emit
472
+ # poll() body, optional __finally_top(), and the factory
473
+ # function in the .cpp. `_is_templated_coro` (not bare
474
+ # type_params) so a protocol-typed param -- which also makes
475
+ # the struct a template -- routes to the header, not an
476
+ # out-of-line .cpp body that would fail to link.
477
+ if not self.gen_async._is_templated_coro(func):
478
+ self.gen_async.gen_coro_poll_def(cpp, func)
479
+ cpp.write("\n")
480
+ self.gen_async.gen_coro_finally_top_def(cpp, func)
481
+ cpp.write("\n")
482
+ self.gen_async.gen_factory(cpp, func)
483
+ cpp.write("\n")
484
+ continue
485
+ if self.functions.is_template_function(func):
486
+ continue
487
+ self.functions.gen_function_def(cpp, func)
488
+ cpp.write("\n")
489
+
490
+ # Method generator __next__() / async __poll__() definitions
491
+ for record in module.all_records():
492
+ for method in record.methods:
493
+ if method.is_generator and not self.gen_generators.is_simple_generator(method):
494
+ # Eligibility is True-or-raises for a non-simple generator
495
+ # method; the call also builds + caches the CFG. Non-template
496
+ # methods emit the __next__ body + finally-top in the .cpp
497
+ # (templated ones emit inline next to the struct in the
498
+ # .hpp, see below).
499
+ if (self._resumable_generator_eligible(method)
500
+ and not self.gen_async._is_templated_coro(method, record.name)):
501
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
502
+ self.gen_async.gen_coro_poll_def(
503
+ cpp, method, record_name=record.name)
504
+ cpp.write("\n")
505
+ self.gen_async.gen_coro_finally_top_def(
506
+ cpp, method, record_name=record.name)
507
+ cpp.write("\n")
508
+ elif method.is_async and not self.gen_async._is_templated_coro(method, record.name):
509
+ # Non-template async methods: poll body lands in .cpp.
510
+ # Template async methods (type params OR a protocol-typed
511
+ # param) have their poll body emitted inline next to the
512
+ # struct in the .hpp (see above).
513
+ self.gen_async.gen_coro_poll_def(
514
+ cpp, method, record_name=record.name)
515
+ cpp.write("\n")
516
+ self.gen_async.gen_coro_finally_top_def(
517
+ cpp, method, record_name=record.name)
518
+ cpp.write("\n")
519
+
520
+ # Non-trivial method bodies live in the .cpp (matches free-function
521
+ # placement). Trivial bodies stay in the .hpp -- emitted earlier by
522
+ # the matching `mode="def_hpp"` pass in `_generate_protocol_ordering`.
523
+ for record in self.records.sort_records_by_inheritance(module.records):
524
+ self.records.gen_record_method_defs(cpp, record, mode="def_cpp")
525
+
526
+ # Generate module init function and main()
527
+ # Always generate __tpy_init for global initialization (Python semantics)
528
+ # Pass ALL top-level statements to init function (including globals)
529
+ # Note: has_user_main=False because users should call main() explicitly at top level,
530
+ # either as `main()` or `if __name__ == "__main__": main()`
531
+ self.functions.gen_module_init_decl(hpp)
532
+ # Pass module name for __name__ variable initialization
533
+ tpy_module_name = self.analyzer.ctx.module_name
534
+ # Imports are now included in top_level_stmts as TpyImport nodes
535
+ # They get emitted as __tpy_init() calls in statement order (Python semantics)
536
+ # Exclude Final globals from init pre-seeding (they live at namespace scope)
537
+ init_globals = {k: v for k, v in seen_globals.items() if k not in self.ctx.final_globals}
538
+ self.functions.gen_module_init(cpp, module.top_level_stmts, init_globals,
539
+ has_user_main=False, module_name=tpy_module_name)
540
+ # Only generate C++ main() for entry point module
541
+ if is_entry_point:
542
+ self.functions.gen_main(cpp, no_main=self.options.no_main)
543
+ else:
544
+ # For non-entry-point modules, just close the namespace
545
+ self.functions.gen_namespace_close(cpp)
546
+
547
+ self._write_header_epilogue(hpp)
548
+
549
+ return hpp.getvalue(), cpp.getvalue()
550
+
551
+ def _resumable_generator_eligible(self, func: "TpyFunction") -> bool:
552
+ """Gate for routing a non-simple generator (free function OR method)
553
+ through the resumable state-machine emitter (`gen_async`). Returns
554
+ False only for the simple-peephole shape (which is emitted inline);
555
+ for any other generator it returns True or raises a clean diagnostic
556
+ -- there is no longer a legacy struct path to fall back to.
557
+ Protocol-typed params are rejected (the for-loop frame field would be
558
+ typed against the abstract concept, not the deduced template param);
559
+ generic generators (`[T]`) are eligible (templated struct + `__next__`
560
+ + factory emit inline in the header). Parameters are captured via the
561
+ async `_classify_params` machinery (value by value, str as
562
+ string_view, reference types by `T&`, `Own[T]` moved in, pointer-repr
563
+ `Optional` as `T*`). Tuple yields use the borrow-form slot via
564
+ `gen_generators._iter_slot_for_yield`.
565
+
566
+ The decision is memoized on the func: the gate is consulted from
567
+ several orchestration passes, and building the CFG per call would be
568
+ wasteful.
569
+ """
570
+ state = resumable_state(func)
571
+ if state.gen_eligible is not None:
572
+ return state.gen_eligible
573
+ result = self._compute_resumable_generator_eligible(func)
574
+ state.gen_eligible = result
575
+ return result
576
+
577
+ def _compute_resumable_generator_eligible(self, func: "TpyFunction") -> bool:
578
+ # Generic generators (explicit `[T]` type params) are eligible: the
579
+ # resumable emitter is template-aware (struct + __next__ + factory
580
+ # emitted inline in the header for templated coros, exactly like
581
+ # generic async defs). For a non-simple generator this returns True
582
+ # or raises a clean diagnostic -- it never returns False (there is
583
+ # no longer a legacy struct path to fall back to).
584
+ if not func.is_generator:
585
+ return False
586
+ # Simple single-yield generators use the lightweight lambda peephole
587
+ # (`gen_simple_generator_inline`); everything else is a resumable
588
+ # frame.
589
+ if self.gen_generators.is_simple_generator(func):
590
+ return False
591
+ if func.generator_yield_type is None:
592
+ return False
593
+ # Protocol-typed params (`def gen(it: Iterable[T])`) make the struct
594
+ # a template (`T_it`), but the resumable for-loop frame field would
595
+ # be typed against the abstract protocol (`Iterable<T>`, a C++
596
+ # concept) instead of the deduced template param -- an ill-formed
597
+ # field decl. That substitution is a separate feature (TODO.md
598
+ # "Generator: protocol-typed params"). (Explicit `[T]` type params
599
+ # have no such issue and stay eligible.)
600
+ if self.protocols.get_all_protocol_params(func.params):
601
+ raise CodeGenError(
602
+ "generators with a protocol-typed parameter are not yet "
603
+ "supported",
604
+ loc=func.loc,
605
+ )
606
+ # Backstop: build (and cache) the CFG, which raises a clean located
607
+ # CodeGenError for any shape the resumable lowering can't handle, so a
608
+ # residual unsupported shape surfaces as a diagnostic rather than a
609
+ # silent miscompile.
610
+ self.gen_async._build_resumable_cfg(func)
611
+ return True
612
+
613
+ def _generate_protocol_ordering(self, hpp: TextIO, module: TpyModule,
614
+ global_decls: list, final_decls: list,
615
+ seen_globals: dict) -> None:
616
+ """Generate protocols, forward declarations, and records in proper order.
617
+
618
+ C++ requires forward declarations and concepts to be defined before use.
619
+ This method handles the complex ordering requirements.
620
+ """
621
+ deps = self._collect_protocol_deps(module)
622
+ # Emit before forward decls/concepts so inline references to imported
623
+ # names are in scope (e.g. struct member initializers using `Rc<T>`).
624
+ self._gen_reexport_using_decls(hpp)
625
+ self._generate_forward_decls_and_concepts(hpp, module, deps)
626
+ self._generate_definitions_and_reexports(hpp, module, global_decls, final_decls, seen_globals, deps)
627
+
628
+ def _is_native_record(self, record_name: str) -> bool:
629
+ """Check if a record is a native import."""
630
+ record_info = self.analyzer.registry.get_record(record_name)
631
+ return record_info is not None and record_info.is_native
632
+
633
+ def _collect_protocol_deps(self, module: TpyModule) -> _ProtocolDeps:
634
+ """Collect all dependency sets needed for protocol ordering."""
635
+ # Exclude native records -- they don't generate C++ structs
636
+ non_native = [r for r in module.records if not self._is_native_record(r.name)]
637
+ deps = _ProtocolDeps(
638
+ module_record_names={r.name for r in non_native},
639
+ records_by_name={r.name: r for r in non_native},
640
+ )
641
+
642
+ # Records referenced in protocol signatures
643
+ for protocol in module.protocols:
644
+ for method_sig in protocol.methods:
645
+ self.protocols.collect_record_types_from_type(method_sig.return_type, deps.protocol_referenced_records)
646
+ for _, param_type in method_sig.params:
647
+ self.protocols.collect_record_types_from_type(param_type, deps.protocol_referenced_records)
648
+ for _, field_type in protocol.fields:
649
+ self.protocols.collect_record_types_from_type(field_type, deps.protocol_referenced_records)
650
+
651
+ # User-defined protocols used as bounds on protocol-referenced records
652
+ for record_name in deps.protocol_referenced_records:
653
+ if record_name in deps.module_record_names:
654
+ record = deps.records_by_name[record_name]
655
+ for bound in record.type_param_bounds.values():
656
+ proto_info = protocol_info_of(bound)
657
+ if proto_info is None or proto_info.cpp_concept is None:
658
+ deps.bound_protocols.add(bound.name)
659
+
660
+ # Records referenced by bound protocols
661
+ for protocol in module.protocols:
662
+ if protocol.name in deps.bound_protocols:
663
+ for method_sig in protocol.methods:
664
+ self.protocols.collect_record_types_from_type(method_sig.return_type, deps.bound_protocol_records)
665
+ for _, param_type in method_sig.params:
666
+ self.protocols.collect_record_types_from_type(param_type, deps.bound_protocol_records)
667
+ for _, field_type in protocol.fields:
668
+ self.protocols.collect_record_types_from_type(field_type, deps.bound_protocol_records)
669
+
670
+ # Records used as type args to bounded records in protocol signatures
671
+ for protocol in module.protocols:
672
+ if protocol.name in deps.bound_protocols:
673
+ continue
674
+ for method_sig in protocol.methods:
675
+ self.protocols.collect_type_args_of_bounded_records(
676
+ method_sig.return_type, deps.records_by_name, deps.early_definition_records
677
+ )
678
+ for _, param_type in method_sig.params:
679
+ self.protocols.collect_type_args_of_bounded_records(
680
+ param_type, deps.records_by_name, deps.early_definition_records
681
+ )
682
+ for _, field_type in protocol.fields:
683
+ self.protocols.collect_type_args_of_bounded_records(
684
+ field_type, deps.records_by_name, deps.early_definition_records
685
+ )
686
+
687
+ # Prereq protocols: user-defined protocols that are bounds on bound_protocol_records
688
+ for record_name in deps.bound_protocol_records:
689
+ if record_name in deps.module_record_names:
690
+ record = deps.records_by_name[record_name]
691
+ for bound in record.type_param_bounds.values():
692
+ proto_info = protocol_info_of(bound)
693
+ if proto_info is None or proto_info.cpp_concept is None:
694
+ deps.prereq_protocols.add(bound.name)
695
+
696
+ # Records referenced by prereq protocols
697
+ for protocol in module.protocols:
698
+ if protocol.name in deps.prereq_protocols:
699
+ for method_sig in protocol.methods:
700
+ self.protocols.collect_record_types_from_type(method_sig.return_type, deps.prereq_protocol_records)
701
+ for _, param_type in method_sig.params:
702
+ self.protocols.collect_record_types_from_type(param_type, deps.prereq_protocol_records)
703
+ for _, field_type in protocol.fields:
704
+ self.protocols.collect_record_types_from_type(field_type, deps.prereq_protocol_records)
705
+
706
+ return deps
707
+
708
+ def _emit_hash_specialization(self, hpp: TextIO, record: TpyRecord) -> None:
709
+ """Emit std::hash specialization for a hashable record.
710
+
711
+ Called immediately after the record's struct definition so that
712
+ subsequent records can use it as a set/dict-key element type.
713
+ `@native`-renamed records use `record_info.native_name` as the
714
+ qualified target (mirrors `_emit_value_type_spec`).
715
+ """
716
+ info = self.analyzer.registry.get_record(record.name)
717
+ if not info or "__hash__" not in info.methods:
718
+ return
719
+ if record.type_params or record.builtin_type_key:
720
+ return
721
+ ns = module_to_cpp_namespace(self.ctx.module_name)
722
+ cpp_name = (qualify_native_name(info.native_name)
723
+ if info.native_name
724
+ else qualified_cpp_name(self.ctx.module_name, record.name))
725
+ hpp.write(f"}} // namespace {ns}\n\n")
726
+ hpp.write(f"template<> struct std::hash<{cpp_name}> {{\n")
727
+ hpp.write(f" size_t operator()(const {cpp_name}& val) const noexcept {{\n")
728
+ hpp.write(f" return static_cast<size_t>(::tpy::__hash__(val));\n")
729
+ hpp.write(f" }}\n")
730
+ hpp.write(f"}};\n")
731
+ hpp.write(f"\nnamespace {ns} {{\n\n")
732
+
733
+ def _emit_nested_hash_specializations(self, hpp: TextIO, record: 'TpyRecord') -> None:
734
+ """Emit std::hash specializations for nested records (recursively).
735
+
736
+ Skips `@native` nested records -- those are emitted by the
737
+ dedicated native-records pass in `_generate_definitions_and_reexports`.
738
+ Symmetric with `_emit_nested_value_type_specs`.
739
+ """
740
+ for nested_rec in record.nested_records:
741
+ if self._is_native_record(nested_rec.name):
742
+ continue
743
+ self._emit_hash_specialization(hpp, nested_rec)
744
+ self._emit_nested_hash_specializations(hpp, nested_rec)
745
+
746
+ def _emit_value_type_spec(self, hpp: TextIO, record: TpyRecord) -> None:
747
+ """Emit `tpy::is_value_type<T>` spec for a ValueType record.
748
+
749
+ Called immediately after the record's struct definition so any
750
+ subsequent inline method body / templated call instantiating on
751
+ the record sees the spec. `@native`-renamed records use
752
+ `record_info.native_name` as the qualified C++ type (the rename
753
+ target lives outside the module's namespace).
754
+
755
+ Note: no `builtin_type_key` guard, asymmetric with
756
+ `_emit_hash_specialization`. `@builtin_type` ValueType records
757
+ (e.g. `Waker`) rely on this emission today -- there's no
758
+ hand-written spec in `runtime/cpp/include/tpy/type_traits.hpp`
759
+ for them, so the codegen path is load-bearing. Revisit if a
760
+ future `@builtin_type` + `@native` ValueType adds a hand-written
761
+ spec; the resulting duplicate `template<>` would be a hard C++
762
+ error pointing here.
763
+ """
764
+ record_info = self.analyzer.registry.get_record(record.name)
765
+ if not record_info or not record_info.is_value_type:
766
+ return
767
+ ns = module_to_cpp_namespace(self.ctx.module_name)
768
+ base_cpp_name = (qualify_native_name(record_info.native_name)
769
+ if record_info.native_name
770
+ else qualified_cpp_name(self.ctx.module_name, record.name))
771
+ hpp.write(f"}} // namespace {ns}\n\n")
772
+ if record.type_params:
773
+ tparams_decl = ", ".join(
774
+ f"std::size_t {tp}" if (i < len(record_info.type_param_kinds)
775
+ and record_info.type_param_kinds[i].name == "INT")
776
+ else f"typename {tp}"
777
+ for i, tp in enumerate(record.type_params)
778
+ )
779
+ tparams_use = ", ".join(record.type_params)
780
+ hpp.write(f"template<{tparams_decl}> struct tpy::is_value_type<{base_cpp_name}<{tparams_use}>> : std::true_type {{}};\n")
781
+ else:
782
+ hpp.write(f"template<> struct tpy::is_value_type<{base_cpp_name}> : std::true_type {{}};\n")
783
+ hpp.write(f"\nnamespace {ns} {{\n\n")
784
+
785
+ def _emit_nested_value_type_specs(self, hpp: TextIO, record: 'TpyRecord') -> None:
786
+ """Emit value-type specs for nested records (recursively).
787
+
788
+ Skips `@native` nested records -- those are emitted by the
789
+ dedicated native-records pass in `_generate_definitions_and_reexports`.
790
+ Emitting here too would produce a duplicate `template<>` (hard C++
791
+ error). Parser today rejects `@native` nested in `@native` but
792
+ permits `@native` nested in a non-native outer, so the duplicate
793
+ path is reachable without this guard.
794
+ """
795
+ for nested_rec in record.nested_records:
796
+ if self._is_native_record(nested_rec.name):
797
+ continue
798
+ self._emit_value_type_spec(hpp, nested_rec)
799
+ self._emit_nested_value_type_specs(hpp, nested_rec)
800
+
801
+ def _emit_concept_and_dynamic(self, hpp: TextIO, protocol: 'TpyProtocol') -> None:
802
+ """Emit concept for a protocol, plus base class if @dynamic.
803
+
804
+ Adapter specializations (::tpy::Adapter, ::tpy::RefAdapter) are emitted
805
+ separately at global scope via _gen_dynamic_adapter_specs().
806
+ """
807
+ from ..parse import TpyProtocol as _TP
808
+ emitted = self.protocols.gen_concept_decl(hpp, protocol)
809
+ if not emitted:
810
+ return
811
+ if protocol.is_dynamic:
812
+ hpp.write("\n")
813
+ self.protocols.gen_dynamic_base_class(hpp, protocol)
814
+ hpp.write("\n")
815
+
816
+ def _generate_forward_decls_and_concepts(
817
+ self, hpp: TextIO, module: TpyModule, deps: _ProtocolDeps
818
+ ) -> None:
819
+ """Generate forward declarations for prereq/bound/protocol-referenced records and concepts."""
820
+ # A protocol method signature can reference a recursive-union wrapper
821
+ # (`def m(self, t: Tree[Int32])` / `def m(self, e: Expr)`), which
822
+ # renders inside the concept body as `Tree<int32_t>` / `Expr` -- so the
823
+ # wrapper must be forward-declared before any concept. Covers both
824
+ # generic (template) and non-generic wrappers. Only needed when
825
+ # protocols exist; otherwise the fwd-decl before records (in
826
+ # _generate_definitions_and_reexports) suffices, and skipping here
827
+ # avoids churning protocol-free recursive-alias snapshots.
828
+ emitted_wrapper_fwd = False
829
+ if module.protocols:
830
+ for name, entry in module.type_aliases.items():
831
+ if name in module.recursive_union_names:
832
+ if entry[2]:
833
+ header = self.protocols.gen_record_template_header(
834
+ entry[2], {}, entry[3])
835
+ hpp.write(f"{header} struct {name};\n")
836
+ else:
837
+ hpp.write(f"struct {name};\n")
838
+ emitted_wrapper_fwd = True
839
+ if emitted_wrapper_fwd:
840
+ hpp.write("\n")
841
+
842
+ # Forward declare records referenced by prereq protocols (unconstrained)
843
+ for record_name in sorted(deps.prereq_protocol_records):
844
+ if record_name in deps.module_record_names:
845
+ record = deps.records_by_name[record_name]
846
+ if record.type_params:
847
+ tparams = ", ".join(f"typename {tp}" for tp in record.type_params)
848
+ hpp.write(f"template<{tparams}> struct {record_name};\n")
849
+ else:
850
+ hpp.write(f"struct {record_name};\n")
851
+
852
+ if deps.prereq_protocol_records & deps.module_record_names:
853
+ hpp.write("\n")
854
+
855
+ # Prereq protocol concepts
856
+ for protocol in module.protocols:
857
+ if protocol.name in deps.prereq_protocols:
858
+ self._emit_concept_and_dynamic(hpp, protocol)
859
+
860
+ # Forward declare records referenced by bound protocols
861
+ for record_name in sorted(deps.bound_protocol_records):
862
+ if record_name in deps.module_record_names:
863
+ if record_name in deps.prereq_protocol_records:
864
+ continue
865
+ record = deps.records_by_name[record_name]
866
+ if record.type_params:
867
+ if record.type_param_bounds or record.type_param_kinds:
868
+ template_header = self.protocols.gen_record_template_header(
869
+ record.type_params, record.type_param_bounds, record.type_param_kinds
870
+ )
871
+ hpp.write(f"{template_header} struct {record_name};\n")
872
+ else:
873
+ tparams = ", ".join(f"typename {tp}" for tp in record.type_params)
874
+ hpp.write(f"template<{tparams}> struct {record_name};\n")
875
+ else:
876
+ hpp.write(f"struct {record_name};\n")
877
+
878
+ if deps.bound_protocol_records & deps.module_record_names:
879
+ hpp.write("\n")
880
+
881
+ # Bound protocol concepts (skip prereq protocols already emitted)
882
+ for protocol in module.protocols:
883
+ if protocol.name in deps.bound_protocols and protocol.name not in deps.prereq_protocols:
884
+ self._emit_concept_and_dynamic(hpp, protocol)
885
+
886
+ # Fully define records referenced by bound protocols
887
+ for record in module.records:
888
+ if record.name in deps.bound_protocol_records:
889
+ self.records.gen_record_decl(hpp, record)
890
+ self._emit_hash_specialization(hpp, record)
891
+ self._emit_nested_hash_specializations(hpp, record)
892
+ self._emit_value_type_spec(hpp, record)
893
+ self._emit_nested_value_type_specs(hpp, record)
894
+ hpp.write("\n")
895
+
896
+ # Full definitions for records that are type args to bounded records
897
+ for record in module.records:
898
+ if record.name in deps.early_definition_records:
899
+ if record.name in deps.bound_protocol_records:
900
+ continue
901
+ self.records.gen_record_decl(hpp, record)
902
+ self._emit_hash_specialization(hpp, record)
903
+ self._emit_nested_hash_specializations(hpp, record)
904
+ self._emit_value_type_spec(hpp, record)
905
+ self._emit_nested_value_type_specs(hpp, record)
906
+ hpp.write("\n")
907
+
908
+ # Forward declare records referenced in protocols (bounds now available)
909
+ for record_name in sorted(deps.protocol_referenced_records):
910
+ if record_name in deps.module_record_names:
911
+ if record_name in deps.early_definition_records:
912
+ continue
913
+ if record_name in deps.bound_protocol_records:
914
+ continue
915
+ if record_name in deps.prereq_protocol_records:
916
+ continue
917
+ record = deps.records_by_name[record_name]
918
+ if record.type_params:
919
+ template_header = self.protocols.gen_record_template_header(
920
+ record.type_params, record.type_param_bounds, record.type_param_kinds
921
+ )
922
+ hpp.write(f"{template_header} struct {record_name};\n")
923
+ else:
924
+ hpp.write(f"struct {record_name};\n")
925
+
926
+ if deps.protocol_referenced_records & deps.module_record_names:
927
+ hpp.write("\n")
928
+
929
+ # Remaining C++20 concepts for user-defined protocols
930
+ for protocol in module.protocols:
931
+ if protocol.name not in deps.bound_protocols and protocol.name not in deps.prereq_protocols:
932
+ self._emit_concept_and_dynamic(hpp, protocol)
933
+
934
+ def _generate_definitions_and_reexports(
935
+ self, hpp: TextIO, module: TpyModule,
936
+ global_decls: list, final_decls: list, seen_globals: dict, deps: _ProtocolDeps
937
+ ) -> None:
938
+ """Generate remaining forward decls, global externs, record definitions, functions, and re-exports."""
939
+ # Hash + is_value_type specs for @native records. The main per-record
940
+ # emit path (`sort_records_by_inheritance`) filters native records out,
941
+ # so without this pass `val_or_ref_t<NativeT>` lands on the false-type
942
+ # default (T&) in generic instantiations -- wrong ABI for a value type --
943
+ # and any TPy-declared `__hash__` on a native record loses its
944
+ # `std::hash` spec, breaking dict/set use. Emitted before any record
945
+ # body / function body that might instantiate on the native type.
946
+ for record in module.all_records():
947
+ if self._is_native_record(record.name):
948
+ self._emit_hash_specialization(hpp, record)
949
+ self._emit_value_type_spec(hpp, record)
950
+
951
+ # Enum class declarations (before records, since records may have enum fields).
952
+ # @native enums skip the declaration entirely -- the user's
953
+ # `# tpy: include(...)` directive provides the C++ enum class.
954
+ for enum in module.enums:
955
+ if enum.is_native:
956
+ continue
957
+ self._gen_enum_decl(hpp, enum)
958
+
959
+ # Top-level EnumUtil specs must be at global scope. Nested enums
960
+ # need their parent struct defined first (emitted after record
961
+ # definitions). @native enums still get EnumUtil (reflection
962
+ # across the binding boundary) but no operator<< -- that's
963
+ # emitted by the user if needed, and TPy's print/repr paths
964
+ # route through __repr__ via the runtime template gated on
965
+ # EnumUtil presence.
966
+ #
967
+ # The `tpy::is_dyn_protocol_base<P>` trait specializations for
968
+ # @dynamic protocols are emitted here too. They have to come
969
+ # before any in-module code that triggers an implicit
970
+ # instantiation of the primary template (which happens whenever
971
+ # `Box<P>`, `Adapter<P, T>`, etc. is referenced -- inline record
972
+ # method bodies emitted later in the same header are the
973
+ # typical culprits). Specializing after that point is a hard
974
+ # C++ error ("specialization after instantiation").
975
+ #
976
+ # The full Adapter / RefAdapter specs are deferred until after
977
+ # records (see below): their override bodies pass protocol-
978
+ # method parameter types (e.g. `Waker`) by value, so those
979
+ # types must be complete at the spec definition site.
980
+ dynamic_protocols = [p for p in module.protocols if p.is_dynamic]
981
+ top_enums = module.enums
982
+ if top_enums or dynamic_protocols:
983
+ ns = module_to_cpp_namespace(self.ctx.module_name)
984
+ hpp.write(f"}} // namespace {ns}\n\n")
985
+ for protocol in dynamic_protocols:
986
+ self.protocols.gen_dyn_protocol_base_trait(hpp, protocol, ns)
987
+ if top_enums:
988
+ self._gen_enum_util_decls_for(hpp, top_enums)
989
+ hpp.write(f"namespace {ns} {{\n\n")
990
+ for enum in top_enums:
991
+ if enum.is_native:
992
+ continue
993
+ self._gen_enum_operator_ostream(hpp, enum)
994
+
995
+ # Forward declare recursive union wrapper structs (before records,
996
+ # so that record fields like Box[JsonValue] can reference the name)
997
+ emitted_fwd = False
998
+ for name, entry in module.type_aliases.items():
999
+ if name in module.recursive_union_names:
1000
+ type_params = entry[2]
1001
+ # Wrappers (generic and non-generic) are forward-declared
1002
+ # before concepts when the module has protocols (see
1003
+ # _generate_forward_decls_and_concepts); avoid a duplicate.
1004
+ if module.protocols:
1005
+ continue
1006
+ if type_params:
1007
+ header = self.protocols.gen_record_template_header(
1008
+ type_params, {}, entry[3])
1009
+ hpp.write(f"{header} struct {name};\n")
1010
+ else:
1011
+ hpp.write(f"struct {name};\n")
1012
+ emitted_fwd = True
1013
+
1014
+ # Forward declare remaining records (excluding native records)
1015
+ for record in module.records:
1016
+ if self._is_native_record(record.name):
1017
+ continue
1018
+ if record.name in deps.protocol_referenced_records:
1019
+ continue
1020
+ if record.name in deps.early_definition_records:
1021
+ continue
1022
+ if record.name in deps.bound_protocol_records:
1023
+ continue
1024
+ if record.name in deps.prereq_protocol_records:
1025
+ continue
1026
+ if record.type_params:
1027
+ template_header = self.protocols.gen_record_template_header(
1028
+ record.type_params, record.type_param_bounds, record.type_param_kinds
1029
+ )
1030
+ hpp.write(f"{template_header} struct {record.name};\n")
1031
+ else:
1032
+ hpp.write(f"struct {record.name};\n")
1033
+ emitted_fwd = True
1034
+ # Ensure spacing between forward decl section and externs when records exist
1035
+ has_non_native_records = any(not self._is_native_record(r.name) for r in module.records)
1036
+ if emitted_fwd or has_non_native_records:
1037
+ hpp.write("\n")
1038
+
1039
+ # Global extern declarations
1040
+ for stmt in global_decls:
1041
+ self.functions.gen_global_extern(hpp, stmt)
1042
+ # Final global declarations (inline constexpr or extern const)
1043
+ for stmt in final_decls:
1044
+ self.functions.gen_final_global_header(hpp, stmt)
1045
+ hpp.write("\n")
1046
+
1047
+ # Imported type alias using-declarations (before function forward
1048
+ # decls so signatures can reference alias names like Shape)
1049
+ emitted_imported_alias = False
1050
+ for local_name, (src_mod, original_name) in sorted(
1051
+ self.analyzer.registry.imported_type_alias_info.items()):
1052
+ # Skip aliases whose C++ emission bypasses the Python name --
1053
+ # the `using PythonName = ...;` would be dead code. See
1054
+ # _emits_own_cpp above.
1055
+ alias_info = self.analyzer.registry.get_type_alias_info(local_name)
1056
+ if alias_info is not None:
1057
+ if alias_info.type_params:
1058
+ # Generic non-recursive aliases have no C++-level
1059
+ # identity (the source module skipped emission), so
1060
+ # emitting `using Local = Src::Original;` would
1061
+ # dangle. See
1062
+ # docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md.
1063
+ continue
1064
+ alias_type = alias_info.body
1065
+ if isinstance(alias_type, NominalType) and _emits_own_cpp(alias_type):
1066
+ continue
1067
+ if not isinstance(alias_type, (NominalType, UnionType)):
1068
+ continue
1069
+ qualified = qualified_cpp_name(src_mod, original_name)
1070
+ if local_name == original_name:
1071
+ hpp.write(f"using {qualified};\n")
1072
+ else:
1073
+ hpp.write(f"using {local_name} = {qualified};\n")
1074
+ emitted_imported_alias = True
1075
+ if emitted_imported_alias:
1076
+ hpp.write("\n")
1077
+
1078
+ # Generator struct forward declarations (so factory forward decls
1079
+ # can reference the struct type name).
1080
+ emitted_gen_fwd = False
1081
+ for func in module.functions:
1082
+ if func.skip_codegen:
1083
+ continue
1084
+ if func.is_generator and not self.gen_generators.is_simple_generator(func):
1085
+ if self._resumable_generator_eligible(func):
1086
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
1087
+ self.gen_async.gen_coro_forward_decl(hpp, func)
1088
+ emitted_gen_fwd = True
1089
+ if func.is_async:
1090
+ self.gen_async.gen_coro_forward_decl(hpp, func)
1091
+ emitted_gen_fwd = True
1092
+ # Method generator/async struct forward declarations (before records)
1093
+ for record in module.all_records():
1094
+ for method in record.methods:
1095
+ if method.is_generator and not self.gen_generators.is_simple_generator(method):
1096
+ if self._resumable_generator_eligible(method):
1097
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
1098
+ self.gen_async.gen_coro_forward_decl(
1099
+ hpp, method, record_name=record.name)
1100
+ emitted_gen_fwd = True
1101
+ if method.is_async:
1102
+ self.gen_async.gen_coro_forward_decl(
1103
+ hpp, method, record_name=record.name)
1104
+ emitted_gen_fwd = True
1105
+ if emitted_gen_fwd:
1106
+ hpp.write("\n")
1107
+
1108
+ # Function forward declarations (before records, so inline
1109
+ # constructor/method bodies can call free functions).
1110
+ # Functions whose signatures reference nested types are deferred
1111
+ # until after record definitions (the parent struct must be complete).
1112
+ deferred_fwd_funcs: list[TpyFunction] = []
1113
+ emitted_fwd_func = False
1114
+ for func in module.functions:
1115
+ if func.skip_codegen:
1116
+ continue
1117
+ if func.is_generator:
1118
+ if self.gen_generators.is_simple_generator(func):
1119
+ continue # Simple generators are inline -- no forward decl
1120
+ if self._resumable_generator_eligible(func):
1121
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
1122
+ if self.gen_async.gen_factory_forward_decl(hpp, func):
1123
+ emitted_fwd_func = True
1124
+ elif func.is_async:
1125
+ if self.gen_async.gen_factory_forward_decl(hpp, func):
1126
+ emitted_fwd_func = True
1127
+ elif _func_uses_nested_type(func):
1128
+ deferred_fwd_funcs.append(func)
1129
+ elif self.functions.gen_function_forward_decl(hpp, func):
1130
+ emitted_fwd_func = True
1131
+ if emitted_fwd_func:
1132
+ hpp.write("\n")
1133
+
1134
+ # Generic recursive-alias wrapper structs are templates: member
1135
+ # completeness is required only at instantiation, not at the template
1136
+ # definition, so they precede records that embed them by value (a
1137
+ # `Tree[Int32]` field needs `Tree` complete). Non-generic wrappers stay
1138
+ # after records -- their variant stores the member records by value, so
1139
+ # those must be complete first.
1140
+ emitted_generic_wrapper = False
1141
+ for name, entry in sorted(module.type_aliases.items()):
1142
+ typ, _loc, type_params, _kinds = entry
1143
+ if name in module.recursive_union_names and type_params:
1144
+ self._gen_recursive_union_struct(hpp, name, typ, type_params, _kinds)
1145
+ emitted_generic_wrapper = True
1146
+ if emitted_generic_wrapper:
1147
+ hpp.write("\n")
1148
+
1149
+ # Full record definitions (skip those already defined early)
1150
+ sorted_records = self.records.sort_records_by_inheritance(module.records)
1151
+ for record in sorted_records:
1152
+ if record.name in deps.early_definition_records:
1153
+ continue
1154
+ if record.name in deps.bound_protocol_records:
1155
+ continue
1156
+ self.records.gen_record_decl(hpp, record)
1157
+ self._emit_hash_specialization(hpp, record)
1158
+ self._emit_nested_hash_specializations(hpp, record)
1159
+ # is_value_type spec MUST precede any subsequent inline method
1160
+ # body / generator struct / coro struct that template-instantiates
1161
+ # on this record. Per-record placement (same pattern as the hash
1162
+ # spec above) makes the ordering structural rather than a
1163
+ # codegen-pass invariant.
1164
+ self._emit_value_type_spec(hpp, record)
1165
+ self._emit_nested_value_type_specs(hpp, record)
1166
+ hpp.write("\n")
1167
+
1168
+ # Deferred forward declarations for functions with nested types
1169
+ for func in deferred_fwd_funcs:
1170
+ self.functions.gen_function_forward_decl(hpp, func)
1171
+ if deferred_fwd_funcs:
1172
+ hpp.write("\n")
1173
+
1174
+ # Dynamic protocol Adapter / RefAdapter specs at global scope.
1175
+ # Emitted here (rather than right after the protocol forward
1176
+ # decl) so that all value-type records referenced by protocol
1177
+ # method signatures are complete -- the Adapter override body
1178
+ # passes those types by value, which a function definition
1179
+ # requires complete. Subsequent in-module use sites (templated
1180
+ # method bodies emitted further down) see the spec.
1181
+ if dynamic_protocols:
1182
+ ns = module_to_cpp_namespace(self.ctx.module_name)
1183
+ hpp.write(f"}} // namespace {ns}\n\n")
1184
+ self._gen_dynamic_adapter_specs(hpp, module, dynamic_protocols)
1185
+ hpp.write(f"namespace {ns} {{\n\n")
1186
+
1187
+ # EnumUtil + operator<< for nested enums (must come after parent struct definitions)
1188
+ nested_enums = [e for e in module.all_enums() if "." in e.name]
1189
+ if nested_enums:
1190
+ ns = module_to_cpp_namespace(self.ctx.module_name)
1191
+ hpp.write(f"}} // namespace {ns}\n\n")
1192
+ self._gen_enum_util_decls_for(hpp, nested_enums)
1193
+ hpp.write(f"namespace {ns} {{\n\n")
1194
+ for enum in nested_enums:
1195
+ # Parser currently rejects nested @native enums, but guard
1196
+ # defensively so a future relaxation can't silently emit
1197
+ # operator<< for a native enum (would conflict with any
1198
+ # user-provided one in their C++ namespace).
1199
+ if enum.is_native:
1200
+ continue
1201
+ self._gen_enum_operator_ostream(hpp, enum)
1202
+
1203
+
1204
+ # Async-method coro struct definitions FIRST (before any free coro
1205
+ # struct that might inline them as `optional<__coro_Class_method>`
1206
+ # sub-future fields -- needs the full type, not just the forward
1207
+ # decl emitted before records). Method factory definitions land
1208
+ # immediately after their struct so the inline factory body sees
1209
+ # the complete coro type.
1210
+ for record in module.all_records():
1211
+ for method in record.methods:
1212
+ if not method.is_async:
1213
+ continue
1214
+ self.gen_async.gen_coro_struct(
1215
+ hpp, method, record_name=record.name)
1216
+ hpp.write("\n")
1217
+ # Templates (type params OR a protocol-typed param OR a
1218
+ # method on a generic class): poll() body must be
1219
+ # inline-in-header, else the out-of-line .cpp body fails to
1220
+ # link against the template struct.
1221
+ if self.gen_async._is_templated_coro(method, record.name):
1222
+ self.gen_async.gen_coro_poll_def(
1223
+ hpp, method, record_name=record.name)
1224
+ hpp.write("\n")
1225
+ self.gen_async.gen_coro_finally_top_def(
1226
+ hpp, method, record_name=record.name)
1227
+ hpp.write("\n")
1228
+ # Templated factory body too: zero-arg generic async defs
1229
+ # would otherwise fall off CTAD.
1230
+ struct_name = self.gen_async._struct_name_templated(method, record.name)
1231
+ cpp_record = escape_cpp_name(record.name.replace(".", "::"))
1232
+ # Out-of-class definition of a member of a class template
1233
+ # qualifies the class name with its template args
1234
+ # (`Box<T>::get()`, not `Box::get()`).
1235
+ record_tps = self.gen_async._record_template_args(record.name)
1236
+ if record_tps:
1237
+ cpp_record = f"{cpp_record}<{', '.join(record_tps)}>"
1238
+ params = self.functions.gen_params(
1239
+ method.params, method.type_params,
1240
+ emit_defaults=False, func=method)
1241
+ args = self.gen_async._factory_args_forwarded(
1242
+ method, receiver=(record.name, "*this"))
1243
+ const_suffix = " const" if method.is_readonly else ""
1244
+ self.gen_async._emit_member_template_headers(hpp, method, record_name=record.name)
1245
+ hpp.write(f"inline {struct_name} {cpp_record}::{method.name}({params}){const_suffix} {{\n")
1246
+ hpp.write(f" return {struct_name}({args});\n")
1247
+ hpp.write(f"}}\n\n")
1248
+
1249
+ # Generator struct full definitions (after records, so struct fields
1250
+ # and inline __next__() can use fully-defined user types).
1251
+ for func in module.functions:
1252
+ if func.skip_codegen:
1253
+ continue
1254
+ if func.is_generator and not self.gen_generators.is_simple_generator(func):
1255
+ if self._resumable_generator_eligible(func):
1256
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
1257
+ self.gen_async.gen_coro_struct(hpp, func)
1258
+ # Templated generators: __next__ body + factory must
1259
+ # be inline-in-header (template definitions can't go
1260
+ # in the .cpp). Emit right after the struct so they
1261
+ # see fully-defined fields. Mirrors the generic
1262
+ # async-def branch below.
1263
+ if self.gen_async._is_templated_coro(func):
1264
+ self.gen_async.gen_coro_poll_def(hpp, func)
1265
+ hpp.write("\n")
1266
+ self.gen_async.gen_coro_finally_top_def(hpp, func)
1267
+ hpp.write("\n")
1268
+ self.gen_async.gen_factory(hpp, func)
1269
+ hpp.write("\n")
1270
+ if func.is_async:
1271
+ self.gen_async.gen_coro_struct(hpp, func)
1272
+ # Templates (type params OR a protocol-typed param): poll()
1273
+ # body must be inline-in-header. Emit it right after the
1274
+ # struct so it sees fully-defined fields.
1275
+ if self.gen_async._is_templated_coro(func):
1276
+ self.gen_async.gen_coro_poll_def(hpp, func)
1277
+ hpp.write("\n")
1278
+ self.gen_async.gen_coro_finally_top_def(hpp, func)
1279
+ hpp.write("\n")
1280
+ self.gen_async.gen_factory(hpp, func)
1281
+ hpp.write("\n")
1282
+ # Method generator struct definitions + out-of-line factory methods
1283
+ for record in module.all_records():
1284
+ for method in record.methods:
1285
+ if method.is_generator and not self.gen_generators.is_simple_generator(method):
1286
+ if self._resumable_generator_eligible(method):
1287
+ with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
1288
+ self.gen_async.gen_coro_struct(
1289
+ hpp, method, record_name=record.name)
1290
+ hpp.write("\n")
1291
+ # Templated generator methods (own type params, a
1292
+ # proto-typed param, OR a method on a generic
1293
+ # class): __next__ body + finally-top must be
1294
+ # inline-in-header.
1295
+ if self.gen_async._is_templated_coro(method, record.name):
1296
+ self.gen_async.gen_coro_poll_def(
1297
+ hpp, method, record_name=record.name)
1298
+ hpp.write("\n")
1299
+ self.gen_async.gen_coro_finally_top_def(
1300
+ hpp, method, record_name=record.name)
1301
+ hpp.write("\n")
1302
+ # Inline factory method (mirrors the async-method
1303
+ # factory: _factory_args_forwarded moves Own[T] /
1304
+ # protocol params in correctly).
1305
+ struct_name = self.gen_async._struct_name_templated(method, record.name)
1306
+ cpp_record = escape_cpp_name(record.name.replace(".", "::"))
1307
+ # Qualify with class template args for
1308
+ # out-of-class member definitions of a class
1309
+ # template (e.g. `Box<T>::items`).
1310
+ record_tps = self.gen_async._record_template_args(record.name)
1311
+ if record_tps:
1312
+ cpp_record = f"{cpp_record}<{', '.join(record_tps)}>"
1313
+ params = self.functions.gen_params(
1314
+ method.params, method.type_params,
1315
+ emit_defaults=False, func=method)
1316
+ args = self.gen_async._factory_args_forwarded(
1317
+ method, receiver=(record.name, "*this"))
1318
+ const_suffix = " const" if method.is_readonly else ""
1319
+ self.gen_async._emit_member_template_headers(hpp, method, record_name=record.name)
1320
+ hpp.write(f"inline {struct_name} {cpp_record}::{method.name}({params}){const_suffix} {{\n")
1321
+ hpp.write(f" return {struct_name}({args});\n")
1322
+ hpp.write(f"}}\n\n")
1323
+
1324
+ # Trivial out-of-line method bodies (single-statement getters /
1325
+ # setters / forwarders) stay in the .hpp with ``inline`` so the
1326
+ # compiler can inline at the call site without LTO. Larger bodies
1327
+ # land in the .cpp via the matching pass at the end of `generate()`.
1328
+ # Cycle members skip the inline-in-header pass: any method body
1329
+ # that touches a cycle peer's type would need that peer's
1330
+ # complete header included from .hpp, which `<peer>_fwd.hpp`
1331
+ # doesn't carry.
1332
+ if not self.ctx.cycle_peers:
1333
+ for record in sorted_records:
1334
+ self.records.gen_record_method_defs(hpp, record, mode="def_hpp")
1335
+
1336
+ # std::hash specializations are emitted per-record inline (see
1337
+ # _emit_hash_specialization), not batched here. This ensures hash
1338
+ # specs appear before any struct that uses the type as a set/dict-key.
1339
+
1340
+ # Module-local type alias definitions (after record definitions
1341
+ # so member types are complete for std::variant).
1342
+ #
1343
+ # Non-recursive generic aliases (`type Pair[T] = ...`) are
1344
+ # expanded at use sites by the parser-resolver (Phase 1 of
1345
+ # generic recursive aliases; see
1346
+ # docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md), so they have no
1347
+ # C++-level identity and we skip emission entirely. The body
1348
+ # still contains TypeParamRef placeholders, which would render
1349
+ # as bare `T` -- invalid C++ at module scope.
1350
+ emitted_alias = False
1351
+ for name, entry in sorted(module.type_aliases.items()):
1352
+ typ, _loc, type_params, _kinds = entry
1353
+ if name in module.recursive_union_names:
1354
+ if type_params:
1355
+ continue # generic recursive wrapper emitted before records
1356
+ self._gen_recursive_union_struct(
1357
+ hpp, name, typ, type_params, _kinds)
1358
+ elif type_params:
1359
+ continue # generic non-recursive alias: no C++ emission
1360
+ else:
1361
+ cpp_type = self.types.type_to_cpp(typ)
1362
+ hpp.write(f"using {name} = {cpp_type};\n")
1363
+ emitted_alias = True
1364
+ if emitted_alias:
1365
+ hpp.write("\n")
1366
+ # Register module-local union aliases AFTER emitting the using
1367
+ # declaration (to avoid circular `using Shape = Shape;`) but
1368
+ # BEFORE function definitions (so signatures use the alias name).
1369
+ # Skip generic aliases: they have no name-level C++ identity, and
1370
+ # registering their substituted body under the alias name would
1371
+ # collide with downstream code that expects union alias names to
1372
+ # be C++-visible.
1373
+ for name, entry in module.type_aliases.items():
1374
+ typ, _loc, type_params, _kinds = entry
1375
+ if not type_params and isinstance(typ, UnionType):
1376
+ register_union_alias(typ.members, name)
1377
+
1378
+ # Function declarations (template definitions, stubs, and extern "C";
1379
+ # non-template signatures are already forward-declared above)
1380
+ emitted_func_decl = False
1381
+ for func in module.functions:
1382
+ if func.skip_codegen:
1383
+ continue
1384
+ if func.is_generator:
1385
+ if self.gen_generators.is_simple_generator(func):
1386
+ # Simple generators: emit inline function definition in header
1387
+ self.gen_generators.gen_simple_generator_inline(hpp, func)
1388
+ hpp.write("\n")
1389
+ emitted_func_decl = True
1390
+ continue # Complex generators: factory emitted in source file
1391
+ if func.is_async:
1392
+ # Async defs go through AsyncCoroCodegen.gen_factory_forward_decl
1393
+ # earlier in the pipeline; gen_function_decl would otherwise
1394
+ # emit a stray sync template stub for generic async defs.
1395
+ continue
1396
+ if self.functions.gen_function_decl(hpp, func):
1397
+ emitted_func_decl = True
1398
+
1399
+ if emitted_func_decl:
1400
+ hpp.write("\n")
1401
+
1402
+ def _gen_reexport_using_decls(self, hpp: TextIO) -> None:
1403
+ """Emit `using` declarations for re-exported symbols (functions /
1404
+ records / enums / variables). One pass over the per-module
1405
+ attribute table buckets cells by kind; each block below emits
1406
+ its `using` / `inline auto&` shape from the bucketed entry.
1407
+ """
1408
+ table = self.analyzer.ctx.module_attributes or {}
1409
+ func_reexports: list[tuple[str, str, str, object]] = []
1410
+ record_reexports: list[tuple[str, str, str, object]] = []
1411
+ protocol_reexports: list[tuple[str, str, str, object]] = []
1412
+ enum_reexports: list[tuple[str, str, str, object]] = []
1413
+ var_reexports: list[tuple[str, str, str, object]] = []
1414
+ for name, cell in table.items():
1415
+ bd = cell.binding
1416
+ if bd.defining_module is None:
1417
+ continue # local definition
1418
+ # The binding's `info` carries the kind-specific payload
1419
+ # (function infos / RecordInfo / enum NominalType / variable
1420
+ # TpyType). Pass it through so the kind-specific blocks
1421
+ # below can apply their filters (decorator stubs, native,
1422
+ # cpp_template, special_handling) even for direct imports
1423
+ # from implicit-stdlib roots where the consumer module's
1424
+ # registry isn't populated.
1425
+ entry = (name, bd.defining_module, bd.canonical_name, bd.info)
1426
+ if bd.kind == SymbolKind.FUNCTION:
1427
+ func_reexports.append(entry)
1428
+ elif bd.kind == SymbolKind.RECORD:
1429
+ record_reexports.append(entry)
1430
+ elif bd.kind == SymbolKind.PROTOCOL_DYNAMIC:
1431
+ # @dynamic protocols compile to abstract base classes
1432
+ # (real types), so a `using X = ns::Base;` alias works.
1433
+ # Static protocols compile to C++ concepts, which need
1434
+ # template-form alias syntax incompatible with the
1435
+ # bucketing emit path; consumers reach them via full
1436
+ # qualification instead.
1437
+ protocol_reexports.append(entry)
1438
+ elif bd.kind == SymbolKind.ENUM:
1439
+ enum_reexports.append(entry)
1440
+ elif bd.kind == SymbolKind.VARIABLE:
1441
+ var_reexports.append(entry)
1442
+ func_reexports.sort()
1443
+ record_reexports.sort()
1444
+ protocol_reexports.sort()
1445
+ enum_reexports.sort()
1446
+ var_reexports.sort()
1447
+
1448
+ # Re-exported functions
1449
+ if func_reexports:
1450
+ any_written = False
1451
+ for local_name, source_module, original_name, info in func_reexports:
1452
+ # Cycle suppression: functions aren't forward-declared
1453
+ # in `<peer>_fwd.hpp`, so a `using` for a cycle peer's
1454
+ # function would reach into the peer's full header.
1455
+ # Consumers qualify through the defining module
1456
+ # directly, so this is interop-surface only.
1457
+ if source_module in self.ctx.cycle_peers:
1458
+ continue
1459
+ # `info` is the FunctionInfo list from the binding (set
1460
+ # at install time); prefer it over registry.get_function
1461
+ # which is None for direct implicit-stdlib imports
1462
+ # bypassing the consumer's local registry.
1463
+ func_infos = info if isinstance(info, list) else (
1464
+ self.ctx.analyzer.registry.get_function(local_name))
1465
+ # For C-linkage functions, emit an extern "C" re-declaration.
1466
+ # Using/alias re-exports don't work because the C++ name may
1467
+ # differ from the Python name (e.g., @native("SDL_GetTicks", binding="C") def get_ticks).
1468
+ # extern "C" re-decls don't reach into a sub-namespace, so
1469
+ # the sibling-cycle suppression below does not apply here.
1470
+ if func_infos and (func_infos[0].is_native_c or func_infos[0].is_extern_c):
1471
+ self.functions.gen_extern_c_redecl(hpp, func_infos[0])
1472
+ any_written = True
1473
+ continue
1474
+ # Skip decorator stubs and native/template functions
1475
+ if func_infos and all(
1476
+ fi.is_decorator_stub or fi.native_name or fi.cpp_template or fi.special_handling
1477
+ for fi in func_infos
1478
+ ):
1479
+ continue
1480
+ # Sibling cycle: the same shape as the cycle_peers check
1481
+ # above, induced by the parent-walk in
1482
+ # `_write_header_preamble` for descendant submodules.
1483
+ # Skipped only after the extern "C" re-decl branch since
1484
+ # that one doesn't reach into the sub-namespace.
1485
+ if self._is_descendant_submodule(source_module):
1486
+ continue
1487
+ qualified = qualified_cpp_name(source_module, original_name)
1488
+ if local_name == original_name:
1489
+ hpp.write(f"using {qualified};\n")
1490
+ else:
1491
+ hpp.write(f"inline auto& {local_name} = {qualified};\n")
1492
+ any_written = True
1493
+ if any_written:
1494
+ hpp.write("\n")
1495
+
1496
+ # Re-exported records / enums. Skip nested types (`.` in name): a
1497
+ # `using ::ns::Container::Inner;` at namespace scope is illegal C++
1498
+ # (using-declaration for a member at non-class scope); the inner
1499
+ # type is accessible through the outer's `using`. Also skip natives
1500
+ # / @builtin_type stubs (no namespace declaration to point at).
1501
+ # Enums also suppress cycle-peer re-exports (revisitable -- the
1502
+ # forward-declared enum in `<peer>_fwd.hpp` would in principle
1503
+ # be `using`'d safely).
1504
+ def _record_skip(info: object, name: str) -> bool:
1505
+ ri = info if isinstance(info, RecordInfo) else (
1506
+ self.analyzer.registry.get_record(name))
1507
+ return ri is not None and (ri.is_native or ri.is_keyword_stub)
1508
+
1509
+ def _enum_skip(info: object, name: str) -> bool:
1510
+ et = info if info is not None else (
1511
+ self.analyzer.registry.get_enum(name))
1512
+ einfo = enum_info_of(et) if et is not None else None
1513
+ return einfo is not None and einfo.is_native
1514
+
1515
+ # Mirrors `protocol_reexports_external` below; downstream record
1516
+ # references already use the fully-qualified `::tpystd::X::Foo`
1517
+ # form, so the unqualified alias would only matter inside this
1518
+ # header and fails the cyclic-include ordering check.
1519
+ record_reexports_external = [
1520
+ e for e in record_reexports
1521
+ if e[1] not in self.ctx.implicit_stdlib_modules
1522
+ ]
1523
+ # When this module is a package re-exporting records/enums from
1524
+ # a descendant submodule, the sub's header pulls in our header
1525
+ # via the parent-walk (`_write_header_preamble`), so the
1526
+ # following `using ::cur::sub::Name;` lines would otherwise
1527
+ # resolve against a not-yet-opened `cur::sub` namespace whenever
1528
+ # the sub is compiled first. Forward-declare those types in
1529
+ # their (relative) sub-namespace so the using-decls resolve at
1530
+ # any include order. Iterate the implicit-stdlib-filtered list
1531
+ # so we don't fwd-decl entries the emit-block will drop anyway.
1532
+ self._emit_sibling_submodule_fwd_decls(
1533
+ hpp, record_reexports_external, enum_reexports,
1534
+ _record_skip, _enum_skip)
1535
+ self._emit_alias_using_block(hpp, record_reexports_external, _record_skip)
1536
+
1537
+ # Re-exported protocols (@dynamic generates an abstract base
1538
+ # class with the protocol's clean name; @static protocols
1539
+ # generate a C++ concept. Either way, a `using` declaration
1540
+ # makes the name accessible in the consuming module's scope so
1541
+ # references like `Box[SomeProto]` resolve at the C++ level.
1542
+ # Native marker protocols (NativeIterable, ValueType, etc.)
1543
+ # don't define a concept at the user-facing name and skip the
1544
+ # `using` -- consumers reach them through `tpy::` qualification.
1545
+ def _protocol_skip(info: object, name: str) -> bool:
1546
+ # info here is a ProtocolInfo (per symbol_binding); skip
1547
+ # native marker protocols (NativeIterable, ValueType, ...).
1548
+ # These map to C++ concepts at fixed ::tpy:: qnames and
1549
+ # don't have a TPy-generated using target.
1550
+ if isinstance(info, ProtocolInfo):
1551
+ return info.cpp_concept is not None
1552
+ return False
1553
+
1554
+ # Filter out implicit-stdlib sources: the stdlib stub build
1555
+ # (each module compiled standalone for caching) doesn't have
1556
+ # later-in-chain headers visible yet when the using-declaration
1557
+ # is processed. Tpy._core importing typing.Iterable etc. is the
1558
+ # canonical case -- those names work via full qualification in
1559
+ # downstream emit paths, so skipping the `using` is safe. The
1560
+ # cross-module case that DOES need the `using` is outside-stdlib
1561
+ # (e.g. asyncio importing protocols from a sibling submodule).
1562
+ # Descendant-submodule sources are NOT dropped here unlike the
1563
+ # function/variable paths above: a static protocol compiles to
1564
+ # a C++ concept (not forward-declarable), and the parent's own
1565
+ # .cpp emits bare protocol names in function signatures
1566
+ # (e.g. `Box<AnyTask>&&` in asyncio.cpp), relying on this
1567
+ # `using` to bring the name into scope. The cycle would only
1568
+ # actually fire if the source sub-module itself triggers the
1569
+ # parent-walk back via a dotted user-module import; that
1570
+ # narrower shape isn't reproduced in the test corpus today.
1571
+ protocol_reexports_external = [
1572
+ e for e in protocol_reexports
1573
+ if e[1] not in self.ctx.implicit_stdlib_modules
1574
+ ]
1575
+ self._emit_alias_using_block(
1576
+ hpp, protocol_reexports_external, _protocol_skip,
1577
+ skip_cycle_peers=True)
1578
+ self._emit_alias_using_block(
1579
+ hpp, enum_reexports, _enum_skip, skip_cycle_peers=True)
1580
+
1581
+ # Re-exported variables. Skip:
1582
+ # - variables whose ultimate source is a native_module (no
1583
+ # namespace path; their use sites resolve via
1584
+ # ModuleVarInfo.native_cpp_name);
1585
+ # - variables sourced from a cycle peer (variables aren't
1586
+ # declared in `<peer>_fwd.hpp`, so a `using` would reach
1587
+ # into the peer's full header);
1588
+ # - variables sourced from a descendant submodule (the sub's
1589
+ # parent-walk pulls us back in, so the `using` would
1590
+ # resolve against a not-yet-opened sub namespace whenever
1591
+ # the sub is compiled first -- mirror of the function path
1592
+ # above). Safe to drop now that `default_to_cpp` routes
1593
+ # default-arg references through the defining module.
1594
+ if var_reexports:
1595
+ any_written = False
1596
+ for local_name, source_module, original_name, _info in var_reexports:
1597
+ if (source_module in self.ctx.cycle_peers
1598
+ or self._is_descendant_submodule(source_module)):
1599
+ continue
1600
+ src_info = self.analyzer.registry.get_module(source_module)
1601
+ if src_info is not None and src_info.is_native_module:
1602
+ continue
1603
+ qualified = qualified_cpp_name(source_module, original_name)
1604
+ hpp.write(f"inline auto& {local_name} = {qualified};\n")
1605
+ any_written = True
1606
+ if any_written:
1607
+ hpp.write("\n")
1608
+
1609
+ def _is_descendant_submodule(self, source_module: str) -> bool:
1610
+ """True when `source_module` is a strict descendant of the
1611
+ current (package) module. Used to identify re-exports that
1612
+ induce a parent<->sub header cycle: the sub's parent-walk
1613
+ emits our header first, so any `using ::cur::sub::Name;`
1614
+ we emit inside our namespace runs while `cur::sub` is mid-
1615
+ parsing and hasn't opened yet.
1616
+ """
1617
+ cur = self.ctx.module_name
1618
+ return source_module.startswith(cur + ".")
1619
+
1620
+ def _emit_sibling_submodule_fwd_decls(
1621
+ self, hpp: TextIO,
1622
+ record_entries: list[tuple[str, str, str, object]],
1623
+ enum_entries: list[tuple[str, str, str, object]],
1624
+ record_skip: Callable[[object, str], bool],
1625
+ enum_skip: Callable[[object, str], bool],
1626
+ ) -> None:
1627
+ """Emit forward declarations of records/enums re-exported from
1628
+ descendant submodules, grouped by relative sub-namespace path.
1629
+ Lives inside our own namespace block, so relative names like
1630
+ `namespace sub { struct X; }` resolve to `cur::sub::X` and
1631
+ satisfy the using-decls emitted just after this block. Skip
1632
+ filters mirror the using-decl emitter so we don't fwd-decl
1633
+ natives, keyword stubs, or nested-name types that the
1634
+ using-decl emitter would also reject.
1635
+ """
1636
+ cur = self.ctx.module_name
1637
+ by_subpath: dict[str, list[str]] = {}
1638
+
1639
+ def add(entry, kind: str, skip_fn: Callable[[object, str], bool]) -> None:
1640
+ local_name, source_module, original_name, info = entry
1641
+ if not self._is_descendant_submodule(source_module):
1642
+ return
1643
+ if "." in local_name or "." in original_name:
1644
+ return
1645
+ if skip_fn(info, original_name):
1646
+ return
1647
+ subpath = source_module[len(cur) + 1:].replace(".", "::")
1648
+ if kind == "record":
1649
+ ri = info if isinstance(info, RecordInfo) else (
1650
+ self.analyzer.registry.get_record(original_name))
1651
+ if ri is not None and ri.type_params:
1652
+ # Class template: mirror the full template header from
1653
+ # `gen_record_template_header` so the forward decl
1654
+ # agrees with the sub-header's declaration.
1655
+ header = self.protocols.gen_record_template_header(
1656
+ ri.type_params, ri.type_param_bounds,
1657
+ ri.type_param_kinds)
1658
+ by_subpath.setdefault(subpath, []).append(
1659
+ f"{header} struct {original_name};")
1660
+ else:
1661
+ by_subpath.setdefault(subpath, []).append(
1662
+ f"struct {original_name};")
1663
+ else:
1664
+ # `enum class Name : underlying;` -- underlying must
1665
+ # match the definition. Resolve via the registered
1666
+ # NominalType (same source the `<peer>_fwd.hpp` path
1667
+ # uses in `generate_fwd_header`).
1668
+ et = info if info is not None else (
1669
+ self.analyzer.registry.get_enum(original_name))
1670
+ einfo = enum_info_of(et) if et is not None else None
1671
+ underlying = (einfo.underlying_type.to_cpp()
1672
+ if einfo is not None else "int32_t")
1673
+ by_subpath.setdefault(subpath, []).append(
1674
+ f"enum class {original_name} : {underlying};")
1675
+
1676
+ for entry in record_entries:
1677
+ add(entry, "record", record_skip)
1678
+ for entry in enum_entries:
1679
+ add(entry, "enum", enum_skip)
1680
+
1681
+ if not by_subpath:
1682
+ return
1683
+ for subpath in sorted(by_subpath):
1684
+ decls = " ".join(by_subpath[subpath])
1685
+ hpp.write(f"namespace {subpath} {{ {decls} }}\n")
1686
+ hpp.write("\n")
1687
+
1688
+ def _emit_alias_using_block(
1689
+ self, hpp: TextIO,
1690
+ entries: list[tuple[str, str, str, object]],
1691
+ skip: Callable[[object, str], bool],
1692
+ *, skip_cycle_peers: bool = False,
1693
+ ) -> None:
1694
+ """Emit `using ::ns::Foo;` (or `using Local = ::ns::Foo;`) for
1695
+ each entry in `entries`, skipping nested types and entries the
1696
+ predicate rejects. Entries are `(local_name, source_module,
1697
+ original_name, info)` tuples sourced from the bucketing pass in
1698
+ `_gen_reexport_using_decls`. Trailing blank line if anything
1699
+ was written.
1700
+ """
1701
+ if not entries:
1702
+ return
1703
+ any_written = False
1704
+ for local_name, source_module, original_name, info in entries:
1705
+ if skip_cycle_peers and source_module in self.ctx.cycle_peers:
1706
+ continue
1707
+ if "." in local_name or "." in original_name:
1708
+ continue
1709
+ if skip(info, original_name):
1710
+ continue
1711
+ qualified = qualified_cpp_name(source_module, original_name)
1712
+ if local_name == original_name:
1713
+ hpp.write(f"using {qualified};\n")
1714
+ else:
1715
+ hpp.write(f"using {local_name} = {qualified};\n")
1716
+ any_written = True
1717
+ if any_written:
1718
+ hpp.write("\n")
1719
+
1720
+ def _gen_recursive_union_struct(
1721
+ self, out: TextIO, name: str, typ: UnionType,
1722
+ type_params: 'list[str] | None' = None,
1723
+ type_param_kinds: 'list | None' = None,
1724
+ ) -> None:
1725
+ """Generate a wrapper struct for a recursive union type alias.
1726
+
1727
+ Non-generic (`type JsonValue = ... | list[JsonValue]`):
1728
+ struct JsonValue {
1729
+ using variant_type = std::variant<...>;
1730
+ variant_type value;
1731
+ JsonValue() = default;
1732
+ template<typename T> requires ... JsonValue(T&& v) : value(...) {}
1733
+ bool operator==(const JsonValue&) const = default;
1734
+ };
1735
+
1736
+ Generic (`type Tree[T] = T | list[Tree[T]]`): one template per alias
1737
+ name. The default ctor / `operator==` are constrained so an
1738
+ instantiation with a non-default-constructible / non-comparable T
1739
+ loses just that member (clean error at the use site) rather than an
1740
+ ill-formed struct. The forwarding ctor's template param uses a
1741
+ sentinel name that cannot collide with a user type param.
1742
+ """
1743
+ cpp_members = [
1744
+ "std::monostate" if is_void_like_type(m) else self.types.type_to_cpp(m)
1745
+ for m in typ.members
1746
+ ]
1747
+ variant_type = f"std::variant<{', '.join(cpp_members)}>"
1748
+ if type_params:
1749
+ header = self.protocols.gen_record_template_header(
1750
+ type_params, {}, type_param_kinds or [])
1751
+ out.write(f"{header}\n")
1752
+ out.write(f"struct {name} {{\n")
1753
+ out.write(f" using variant_type = {variant_type};\n")
1754
+ out.write(f" variant_type value;\n\n")
1755
+ out.write(f" {name}() requires std::default_initializable<variant_type> = default;\n")
1756
+ out.write(f" template<typename _TpyAliasCtorArg>\n")
1757
+ out.write(f" requires std::constructible_from<variant_type, _TpyAliasCtorArg&&>\n")
1758
+ out.write(f" {name}(_TpyAliasCtorArg&& v) : value(std::forward<_TpyAliasCtorArg>(v)) {{}}\n\n")
1759
+ out.write(f" bool operator==(const {name}&) const "
1760
+ f"requires std::equality_comparable<variant_type> = default;\n\n")
1761
+ out.write(f" friend std::ostream& operator<<(std::ostream& os, const {name}& v) {{\n")
1762
+ out.write(f" ::tpy::detail::print_element(os, v.value);\n")
1763
+ out.write(f" return os;\n")
1764
+ out.write(f" }}\n")
1765
+ out.write(f"}};\n")
1766
+ return
1767
+ out.write(f"struct {name} {{\n")
1768
+ out.write(f" using variant_type = {variant_type};\n")
1769
+ out.write(f" variant_type value;\n\n")
1770
+ out.write(f" {name}() = default;\n")
1771
+ out.write(f" template<typename T>\n")
1772
+ out.write(f" requires std::constructible_from<variant_type, T&&>\n")
1773
+ out.write(f" {name}(T&& v) : value(std::forward<T>(v)) {{}}\n\n")
1774
+ out.write(f" bool operator==(const {name}&) const = default;\n\n")
1775
+ out.write(f" friend std::ostream& operator<<(std::ostream& os, const {name}& v) {{\n")
1776
+ out.write(f" ::tpy::detail::print_element(os, v.value);\n")
1777
+ out.write(f" return os;\n")
1778
+ out.write(f" }}\n")
1779
+ out.write(f"}};\n")
1780
+
1781
+ def _gen_enum_decl(self, out: TextIO, enum) -> None:
1782
+ """Generate C++ enum class declaration (no helpers)."""
1783
+ enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
1784
+ if not enum_type:
1785
+ return
1786
+ underlying = enum_info_of(enum_type).underlying_type.to_cpp()
1787
+
1788
+ out.write(f"enum class {enum.name} : {underlying} {{\n")
1789
+ for member_name, value, _ in enum.members:
1790
+ out.write(f" {member_name} = {value},\n")
1791
+ out.write("};\n\n")
1792
+
1793
+ def _gen_dynamic_adapter_specs(self, out: TextIO, module: TpyModule,
1794
+ dynamic_protocols: list) -> None:
1795
+ """Generate ::tpy::Adapter/RefAdapter partial specializations (global scope)."""
1796
+ ns = module_to_cpp_namespace(self.ctx.module_name)
1797
+ # User-namespace type names that must be qualified inside `tpy::`
1798
+ # adapter overrides; see ProtocolGenerator._qualify_user_types.
1799
+ user_type_names = {r.name for r in module.records} | {p.name for p in module.protocols}
1800
+ for protocol in dynamic_protocols:
1801
+ self.protocols.gen_dynamic_adapter_specs(out, protocol, ns, user_type_names)
1802
+ out.write("\n")
1803
+
1804
+ def _gen_enum_util_decls_for(self, out: TextIO, enums: list) -> None:
1805
+ """Generate ::tpy::EnumUtil<E> specialization declarations for given enums."""
1806
+ from .context import enum_cpp_name
1807
+ cur_module = self.ctx.module_name
1808
+ for enum in enums:
1809
+ enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
1810
+ if not enum_type:
1811
+ continue
1812
+ underlying = enum_info_of(enum_type).underlying_type.to_cpp()
1813
+ qualified = enum_cpp_name(enum_type, cur_module, absolute=True)
1814
+ short_name = bare_name(enum.name)
1815
+ member_count = len(enum.members)
1816
+ out.write(f"template<>\n")
1817
+ out.write(f"struct tpy::EnumUtil<{qualified}> {{\n")
1818
+ out.write(f" static constexpr std::string_view type_name = \"{short_name}\";\n")
1819
+ out.write(f" static std::string_view name({qualified} e);\n")
1820
+ out.write(f" static const std::array<{qualified}, {member_count}> members;\n")
1821
+ out.write(f" static {qualified} from_value({underlying} v);\n")
1822
+ out.write(f" static {qualified} from_name(std::string_view s);\n")
1823
+ out.write(f" static std::optional<{qualified}> try_parse(std::string_view s);\n")
1824
+ out.write(f"}};\n\n")
1825
+
1826
+ def _gen_enum_operator_ostream(self, out: TextIO, enum) -> None:
1827
+ """Generate inline operator<< inside user namespace."""
1828
+ cpp_name = enum.name.replace(".", "::")
1829
+ # Use short name for repr to match CPython (Kind.TEXT, not Message.Kind.TEXT)
1830
+ short_name = bare_name(enum.name)
1831
+ out.write(f"inline std::ostream& operator<<(std::ostream& __os, {cpp_name} __e) {{\n")
1832
+ out.write(f" return __os << \"{short_name}.\" << ::tpy::EnumUtil<{cpp_name}>::name(__e);\n")
1833
+ out.write(f"}}\n\n")
1834
+
1835
+ def _gen_enum_source_defs(self, out: TextIO, module: TpyModule) -> None:
1836
+ """Generate ::tpy::EnumUtil<E> member definitions in namespace tpy."""
1837
+ from .context import enum_cpp_name
1838
+ cur_module = self.ctx.module_name
1839
+ out.write("namespace tpy {\n\n")
1840
+ for enum in module.all_enums():
1841
+ enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
1842
+ if not enum_type:
1843
+ continue
1844
+ einfo = enum_info_of(enum_type)
1845
+ underlying = einfo.underlying_type.to_cpp()
1846
+ qualified = enum_cpp_name(enum_type, cur_module, absolute=True)
1847
+ short_name = bare_name(enum.name)
1848
+ member_count = len(enum.members)
1849
+ # cpp_of(python_name) -> C++ enumerator name (Python name if no
1850
+ # native_member() override). Used at every site that emits a
1851
+ # `qualified::<member>` C++ symbol reference. Sites that emit
1852
+ # the Python-side string (name() return value, try_parse key)
1853
+ # keep the Python name. Built once per enum to avoid O(N**2)
1854
+ # over the per-member emission loops.
1855
+ _cpp_map = einfo.cpp_member_name_map
1856
+ def cpp_of(n: str, _m: dict[str, str] = _cpp_map) -> str:
1857
+ return _m.get(n, n)
1858
+
1859
+ # @native enum + explicit values: pin each declared value to the
1860
+ # C++ side at compile time. Lives in the .cpp (not the .hpp) so it
1861
+ # doesn't bloat every translation unit that includes the header --
1862
+ # the assertion still fires at the same point, when this .cpp
1863
+ # compiles. auto()/native_member() opt out -- the user did not
1864
+ # declare a value.
1865
+ if enum.is_native and enum.has_explicit_values:
1866
+ for member_name, value, _ in enum.members:
1867
+ out.write(
1868
+ f"static_assert(static_cast<{underlying}>({qualified}::{cpp_of(member_name)}) == {value}, "
1869
+ f"\"TPy-declared value for {short_name}.{member_name} does not match C++ side\");\n"
1870
+ )
1871
+ out.write("\n")
1872
+
1873
+ # name()
1874
+ out.write(f"std::string_view EnumUtil<{qualified}>::name({qualified} __e) {{\n")
1875
+ out.write(f" switch (__e) {{\n")
1876
+ for member_name, _, _ in enum.members:
1877
+ out.write(
1878
+ f" case {qualified}::{cpp_of(member_name)}: "
1879
+ f"return \"{member_name}\";\n"
1880
+ )
1881
+ # Internal invariant: TPy enum values are always one of the declared
1882
+ # cases, so the default is unreachable from well-typed code. Stays
1883
+ # panic (not raise<ValueError>) -- the from_value path below already
1884
+ # raises ValueError for user-supplied invalid values.
1885
+ out.write(f" default: tpy_panic(\"invalid enum value\");\n")
1886
+ out.write(f" }}\n")
1887
+ out.write(f"}}\n\n")
1888
+
1889
+ # members
1890
+ out.write(f"const std::array<{qualified}, {member_count}>\n")
1891
+ out.write(f"EnumUtil<{qualified}>::members = {{\n")
1892
+ for member_name, _, _ in enum.members:
1893
+ out.write(f" {qualified}::{cpp_of(member_name)},\n")
1894
+ out.write(f"}};\n\n")
1895
+
1896
+ # from_value(). For @native enums the C++ side is the source
1897
+ # of truth for member values, so cases key off the C++
1898
+ # enumerator's actual value (not the TPy-declared int, which
1899
+ # may be `auto()`-assigned and meaningless for the binding).
1900
+ out.write(f"{qualified} EnumUtil<{qualified}>::from_value({underlying} __v) {{\n")
1901
+ out.write(f" switch (__v) {{\n")
1902
+ for member_name, value, _ in enum.members:
1903
+ if enum.is_native:
1904
+ out.write(
1905
+ f" case static_cast<{underlying}>({qualified}::{cpp_of(member_name)}): "
1906
+ f"return {qualified}::{cpp_of(member_name)};\n"
1907
+ )
1908
+ else:
1909
+ out.write(f" case {value}: return {qualified}::{member_name};\n")
1910
+ out.write(f" default: raise<ValueError>(\"{{}} is not a valid {enum.name}\", __v);\n")
1911
+ out.write(f" }}\n")
1912
+ out.write(f"}}\n\n")
1913
+
1914
+ # try_parse(): keys are Python-side names (user-facing), returns
1915
+ # are C++ enumerators (which may differ for @native enums).
1916
+ out.write(f"std::optional<{qualified}> EnumUtil<{qualified}>::try_parse(std::string_view __name) {{\n")
1917
+ member_names = [name for name, _, _ in enum.members]
1918
+ if len(member_names) >= STRING_SWITCH_THRESHOLD:
1919
+ self._gen_enum_try_parse_switch(out, qualified, member_names, cpp_of)
1920
+ else:
1921
+ for member_name in member_names:
1922
+ out.write(
1923
+ f" if (__name == \"{member_name}\") "
1924
+ f"return {qualified}::{cpp_of(member_name)};\n"
1925
+ )
1926
+ out.write(f" return std::nullopt;\n")
1927
+ out.write(f"}}\n\n")
1928
+
1929
+ # from_name() delegates to try_parse()
1930
+ out.write(f"{qualified} EnumUtil<{qualified}>::from_name(std::string_view __name) {{\n")
1931
+ out.write(f" auto __result = try_parse(__name);\n")
1932
+ out.write(f" if (!__result.has_value()) raise<KeyError>(\"{{}}\", __name);\n")
1933
+ out.write(f" return *__result;\n")
1934
+ out.write(f"}}\n\n")
1935
+
1936
+ out.write("} // namespace tpy\n\n")
1937
+
1938
+ @staticmethod
1939
+ def _gen_enum_try_parse_switch(
1940
+ out: TextIO, qualified: str,
1941
+ member_names: list[str],
1942
+ cpp_of: Callable[[str], str],
1943
+ ) -> None:
1944
+ """Generate switch-based try_parse for enum with many members.
1945
+
1946
+ `cpp_of` maps Python-side member names to C++ enumerator names
1947
+ (identity for tpy-defined enums; honors native_member() overrides
1948
+ for @native enums). Members are keyed by Python name (user-facing
1949
+ string), returned as their C++ enumerator.
1950
+ """
1951
+ kind, param, _buckets = find_best_discriminator(member_names)
1952
+
1953
+ # Build bucket -> [member_name] mapping
1954
+ buckets: dict[int, list[str]] = {}
1955
+ for name in member_names:
1956
+ key = len(name) if kind == "length" else ord(name[param])
1957
+ buckets.setdefault(key, []).append(name)
1958
+
1959
+ if kind == "length":
1960
+ out.write(f" switch (__name.size()) {{\n")
1961
+ case_indent = " "
1962
+ body_indent = " "
1963
+ else:
1964
+ out.write(f" if (__name.size() >= {param + 1}) {{\n")
1965
+ out.write(f" switch (static_cast<unsigned char>(__name[{param}])) {{\n")
1966
+ case_indent = " "
1967
+ body_indent = " "
1968
+
1969
+ for disc_value in sorted(buckets.keys()):
1970
+ names = buckets[disc_value]
1971
+ if kind == "char_at":
1972
+ ch = chr(disc_value)
1973
+ out.write(f"{case_indent}case '{escape_cpp_char(ch)}': {{\n")
1974
+ else:
1975
+ out.write(f"{case_indent}case {disc_value}: {{\n")
1976
+ for name in names:
1977
+ out.write(
1978
+ f"{body_indent}if (__name == \"{name}\") "
1979
+ f"return {qualified}::{cpp_of(name)};\n"
1980
+ )
1981
+ out.write(f"{body_indent}break;\n")
1982
+ out.write(f"{case_indent}}}\n")
1983
+
1984
+ out.write(f"{case_indent}}}\n")
1985
+ if kind == "char_at":
1986
+ out.write(f" }}\n")
1987
+
1988
+ def _module_to_include_path(self, module_name: str, *,
1989
+ prefer_fwd: bool = False) -> str:
1990
+ """Convert dotted module name to include path.
1991
+
1992
+ When `prefer_fwd` is True AND `module_name` is a cycle peer of
1993
+ the current module, returns `<mod>_fwd.hpp` instead of
1994
+ `<mod>.hpp`. The fwd header carries forward declarations of
1995
+ the peer's records / enums / @dynamic protocols so the cyclic
1996
+ include resolves without requiring complete types.
1997
+ """
1998
+ from .context import module_to_include_path
1999
+ path = module_to_include_path(module_name)
2000
+ if prefer_fwd and module_name in self.ctx.cycle_peers:
2001
+ if path.endswith(".hpp"):
2002
+ return path[:-len(".hpp")] + "_fwd.hpp"
2003
+ return path
2004
+
2005
+ def _write_header_preamble(self, out: TextIO,
2006
+ native_funcs: list[TpyFunction] | None = None,
2007
+ native_globals: list[TpyVarDecl] | None = None,
2008
+ directives: ModuleDirectives | None = None) -> None:
2009
+ out.write("// Generated by TurboPython Compiler\n")
2010
+ out.write("#pragma once\n\n")
2011
+ out.write('#include <tpy/tpy.hpp>\n')
2012
+ included: set[str] = set()
2013
+ if directives and directives.includes:
2014
+ for include_path, platform in directives.includes:
2015
+ if not _platform_matches(platform):
2016
+ continue
2017
+ if include_path.startswith('<') and include_path.endswith('>'):
2018
+ out.write(f'#include {include_path}\n')
2019
+ else:
2020
+ out.write(f'#include "{include_path}"\n')
2021
+ included.add(include_path)
2022
+ out.write('\n')
2023
+ # Skip implicit stdlib peers we don't actually depend on -- auto-
2024
+ # including unrelated peers creates cycles when two stdlib modules
2025
+ # cross-reference each other. Strip self-parent prefixes because
2026
+ # qname-derived reach collapses `tpy.Foo` to "tpy" even for types
2027
+ # defined in this module.
2028
+ own_deps = set(self.ctx.user_module_imports) | set(self.analyzer.ctx.reached)
2029
+ own_parents = {
2030
+ ".".join(self.ctx.module_name.split(".")[:i + 1])
2031
+ for i in range(self.ctx.module_name.count("."))
2032
+ }
2033
+ own_parents.add(self.ctx.module_name)
2034
+ own_deps -= own_parents
2035
+ # Closed-under-prefixes set of deps: dep `tpy._core._types` makes
2036
+ # peer `tpy._core` match (peer is an ancestor of a dep). Direction
2037
+ # 1 (peer descends from a dep) still keys on `own_deps` itself --
2038
+ # NOT this closure -- because parent-stripping above removed
2039
+ # `own_parents` from `own_deps` to suppress self-refs, and a
2040
+ # full prefix-closure would re-add them.
2041
+ dep_prefix_closure = set(own_deps)
2042
+ for dep in own_deps:
2043
+ dep_parts = dep.split(".")
2044
+ for i in range(1, len(dep_parts)):
2045
+ dep_prefix_closure.add(".".join(dep_parts[:i]))
2046
+
2047
+ def _dep_match(peer: str) -> bool:
2048
+ if peer in dep_prefix_closure:
2049
+ return True
2050
+ peer_parts = peer.split(".")
2051
+ for i in range(1, len(peer_parts)):
2052
+ if ".".join(peer_parts[:i]) in own_deps:
2053
+ return True
2054
+ return False
2055
+
2056
+ for implicit_mod in sorted(self.ctx.implicit_stdlib_modules):
2057
+ if implicit_mod == self.ctx.module_name:
2058
+ continue
2059
+ mod_info = self.analyzer.registry.get_module(implicit_mod)
2060
+ if mod_info and not mod_info.generates_header:
2061
+ continue
2062
+ if not _dep_match(implicit_mod):
2063
+ continue
2064
+ if implicit_mod in self.ctx.all_user_modules:
2065
+ include_path = self._module_to_include_path(implicit_mod)
2066
+ out.write(f'#include "{include_path}"\n')
2067
+ included.add(implicit_mod)
2068
+ # Include user module headers (including parent packages for dotted imports).
2069
+ # Driven by the union of user_module_imports (today's literal-import set)
2070
+ # and module_reached (types referenced through field/method chains).
2071
+ # For native modules we recurse through their own reach so that
2072
+ # # tpy: include() directives flow across native-to-native chains --
2073
+ # natives have no .hpp to chain through, so the consumer must emit
2074
+ # them all directly.
2075
+ chase_visited: set[str] = set()
2076
+
2077
+ def emit_native_includes(info) -> None:
2078
+ for inc, platform in info.includes:
2079
+ if not _platform_matches(platform):
2080
+ continue
2081
+ if inc in included:
2082
+ continue
2083
+ if inc.startswith('<') and inc.endswith('>'):
2084
+ out.write(f'#include {inc}\n')
2085
+ else:
2086
+ out.write(f'#include "{inc}"\n')
2087
+ included.add(inc)
2088
+
2089
+ def emit_non_native(mod_name: str) -> None:
2090
+ if mod_name == self.ctx.module_name or mod_name in included:
2091
+ return
2092
+ # Parent package headers first, for dotted modules
2093
+ parts = mod_name.split('.')
2094
+ for i in range(1, len(parts)):
2095
+ parent_pkg = '.'.join(parts[:i])
2096
+ if parent_pkg == self.ctx.module_name:
2097
+ continue
2098
+ if parent_pkg in self.ctx.all_user_modules and parent_pkg not in included:
2099
+ parent_info = self.analyzer.registry.get_module(parent_pkg)
2100
+ if parent_info is not None and not parent_info.generates_header:
2101
+ # Native-module package init has no .hpp; surface its
2102
+ # raw includes (if any) and mark as visited so we don't
2103
+ # try again for sibling submodules.
2104
+ emit_native_includes(parent_info)
2105
+ included.add(parent_pkg)
2106
+ continue
2107
+ out.write(f'#include "{self._module_to_include_path(parent_pkg, prefer_fwd=True)}"\n')
2108
+ included.add(parent_pkg)
2109
+ out.write(f'#include "{self._module_to_include_path(mod_name, prefer_fwd=True)}"\n')
2110
+ included.add(mod_name)
2111
+
2112
+ def visit(mod_name: str) -> None:
2113
+ if mod_name in chase_visited or mod_name == self.ctx.module_name:
2114
+ return
2115
+ chase_visited.add(mod_name)
2116
+ mod_info = self.analyzer.registry.get_module(mod_name)
2117
+ if mod_info is None:
2118
+ return
2119
+ if mod_info.is_builtin:
2120
+ return
2121
+ if not mod_info.generates_header:
2122
+ # Skip implicit stdlib native shims (decorator stubs, no C++)
2123
+ if mod_name in self.ctx.implicit_stdlib_modules:
2124
+ return
2125
+ emit_native_includes(mod_info)
2126
+ # Natives have no .hpp to chain through, so the consumer must
2127
+ # see every reach the native exposes -- recurse explicitly.
2128
+ for next_mod in sorted(mod_info.reached):
2129
+ visit(next_mod)
2130
+ return
2131
+ # Non-native: emit the hpp; preprocessor handles its transitive deps.
2132
+ emit_non_native(mod_name)
2133
+
2134
+ seed = set(self.ctx.user_module_imports)
2135
+ seed.update(self.analyzer.ctx.reached)
2136
+ for user_mod in sorted(seed):
2137
+ visit(user_mod)
2138
+ # Include macro dependency module headers (from MACRO_DEPS)
2139
+ for dep_mod in sorted(self.ctx.macro_dep_modules):
2140
+ if dep_mod in included or dep_mod == self.ctx.module_name:
2141
+ continue
2142
+ module_info = self.analyzer.registry.get_module(dep_mod)
2143
+ if module_info and (module_info.is_builtin or not module_info.generates_header):
2144
+ continue
2145
+ if dep_mod in self.ctx.all_user_modules:
2146
+ include_path = self._module_to_include_path(dep_mod)
2147
+ out.write(f'#include "{include_path}"\n')
2148
+ included.add(dep_mod)
2149
+ out.write("\n")
2150
+
2151
+ # Native global extern declarations go before the tpyapp namespace
2152
+ if native_globals:
2153
+ for stmt in native_globals:
2154
+ cpp_name = stmt.native_name or stmt.name
2155
+ var_type = resolve_stmt_type_cascade(stmt, self.analyzer, self.types)
2156
+ if stmt.linkage == VarLinkage.NATIVE_C_ARRAY:
2157
+ # C array global: Ptr[T] -> extern "C" T name[];
2158
+ # The incomplete array type decays to T* when used.
2159
+ if isinstance(var_type, PtrType):
2160
+ elem_cpp = var_type.pointee.to_cpp()
2161
+ else:
2162
+ elem_cpp = var_type.to_cpp()
2163
+ out.write(f'extern "C" {elem_cpp} {cpp_name}[];\n')
2164
+ elif stmt.linkage == VarLinkage.NATIVE_C:
2165
+ cpp_type = var_type.to_cpp()
2166
+ out.write(f'extern "C" {cpp_type} {cpp_name};\n')
2167
+ else:
2168
+ cpp_type = var_type.to_cpp()
2169
+ ns, bare = FunctionGenerator._split_native_name(cpp_name)
2170
+ if ns:
2171
+ out.write(f"namespace {ns} {{ extern {cpp_type} {bare}; }}\n")
2172
+ else:
2173
+ out.write(f"extern {cpp_type} {bare};\n")
2174
+ out.write("\n")
2175
+
2176
+ # Use nested namespace for dotted module names
2177
+ ns = module_to_cpp_namespace(self.ctx.module_name)
2178
+ out.write(f"namespace {ns} {{\n\n")
2179
+
2180
+ def generate_fwd_header(self, module: TpyModule, module_name: str) -> str:
2181
+ """Emit `<mod>_fwd.hpp` -- forward declarations of every record /
2182
+ enum / @dynamic protocol defined in this module's namespace,
2183
+ plus the namespace skeleton itself. Cycle peers in the same SCC
2184
+ include this header in place of `<mod>.hpp` to break the
2185
+ cyclic complete-type include while keeping access to the type
2186
+ names.
2187
+
2188
+ Carries declarations only (no method bodies, no field
2189
+ layouts), so positions that need complete-type info (by-value
2190
+ fields, container elements, concrete inheritance, ...) are
2191
+ rejected up-front by `_check_workspace_completeness_cycles`.
2192
+ """
2193
+ from .context import module_to_cpp_namespace
2194
+ ns = module_to_cpp_namespace(module_name)
2195
+ out_buf = io.StringIO()
2196
+ out_buf.write("// Generated by TurboPython Compiler -- forward declarations for cycle peers\n")
2197
+ out_buf.write("#pragma once\n\n")
2198
+ out_buf.write(f"namespace {ns} {{\n\n")
2199
+ for record in module.all_records():
2200
+ cpp_name = record.name.replace(".", "::")
2201
+ out_buf.write(f"struct {cpp_name};\n")
2202
+ for enum in module.all_enums():
2203
+ # @native enums: skip the forward decl. The user's
2204
+ # `# tpy: include(...)` provides the type; re-declaring with
2205
+ # the TPy-recorded underlying type would risk ODR mismatch.
2206
+ if enum.is_native:
2207
+ continue
2208
+ cpp_name = enum.name.replace(".", "::")
2209
+ # `enum class Name : underlying;` -- the underlying type
2210
+ # in the forward declaration MUST agree with the
2211
+ # definition's. Pull it from the EnumInfo attached to
2212
+ # the registered NominalType so both this fwd header
2213
+ # and the full header in `_gen_enum_decl` use the same
2214
+ # type and the C++ compiler accepts the redeclaration.
2215
+ enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
2216
+ if enum_type is None:
2217
+ # Skeleton-pre-pop NominalType lacks an EnumInfo until
2218
+ # `register_enum` runs; default to `int32_t` (matches
2219
+ # `register_enum`'s default). The peer's full header
2220
+ # will redeclare with the same underlying type.
2221
+ underlying = "int32_t"
2222
+ else:
2223
+ underlying = enum_info_of(enum_type).underlying_type.to_cpp()
2224
+ out_buf.write(f"enum class {cpp_name} : {underlying};\n")
2225
+ for protocol in module.protocols:
2226
+ # @dynamic protocols emit a `struct {Name}` base class that
2227
+ # IS forward-declarable. Static (structural) protocols emit
2228
+ # only a C++20 concept, which is NOT forward-declarable;
2229
+ # cycle peers using a static protocol's name as a template
2230
+ # constraint must include the full peer header (a complete-
2231
+ # type position for the completeness-graph reject gate).
2232
+ if protocol.is_dynamic:
2233
+ ProtocolGenerator.emit_dynamic_base_forward_decl(out_buf, protocol)
2234
+ out_buf.write(f"\n}} // namespace {ns}\n")
2235
+ return out_buf.getvalue()
2236
+
2237
+ def _write_source_preamble(self, out: TextIO, module: TpyModule) -> None:
2238
+ out.write("// Generated by TurboPython Compiler\n")
2239
+ # Use full include path from include root (consistent with header includes)
2240
+ include_path = self._module_to_include_path(self.ctx.module_name)
2241
+ out.write(f'#include "{include_path}"\n')
2242
+ # Cycle peers: the .hpp included <peer>_fwd.hpp for each, but
2243
+ # the .cpp needs the FULL <peer>.hpp so function bodies can
2244
+ # see complete types of cycle peers' records.
2245
+ for peer in sorted(self.ctx.cycle_peers):
2246
+ if peer == self.ctx.module_name:
2247
+ continue
2248
+ out.write(f'#include "{self._module_to_include_path(peer)}"\n')
2249
+ out.write("\n")
2250
+ # EnumUtil definitions go before user namespace (they live in namespace tpy)
2251
+ if module.all_enums():
2252
+ self._gen_enum_source_defs(out, module)
2253
+ ns = module_to_cpp_namespace(self.ctx.module_name)
2254
+ out.write(f"namespace {ns} {{\n\n")
2255
+
2256
+ def _write_header_epilogue(self, out: TextIO) -> None:
2257
+ ns = module_to_cpp_namespace(self.ctx.module_name)
2258
+ out.write(f"}} // namespace {ns}\n")