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,1651 @@
1
+ """Control-flow graph for resumable-frame lowering (async def today; the
2
+ future generator migration consumes the same module).
3
+
4
+ A resumable frame is a state-machine struct whose body method (`__poll__`
5
+ for async, `__next__` for generators) dispatches on a state integer to
6
+ the right resume point. To support `await` (or `yield`) inside arbitrary
7
+ control flow -- if/while/for/try/with -- we lower the function body to
8
+ a CFG of basic blocks, then emit each suspension's resume case body
9
+ wrapped in the source-level try/except/finally/with stack that was
10
+ active at that suspension point. C++'s rule that case labels inside try
11
+ blocks aren't reachable from outside the try forces this "replay the
12
+ region stack inside each case" pattern; it's what Roslyn's async
13
+ rewriter does for the same reason.
14
+
15
+ This module owns:
16
+ - Basic-block + terminator + region data types.
17
+ - The CFG builder that walks a resumable (async def / generator) body.
18
+ - (Future task) Conservative cross-suspension liveness.
19
+ - (Future task) State assignment.
20
+
21
+ Emission is in `gen_async.py` (and, later, `gen_generators.py`); the
22
+ CFG itself is shape-neutral.
23
+
24
+ Generator migration plan: SuspensionPayload is a tagged union with two
25
+ variants -- AwaitPayload (async) and YieldPayload (generator). The
26
+ builder dispatches on the node it encounters: a top-level `await` shape
27
+ produces an AwaitPayload, a `yield` statement produces a YieldPayload,
28
+ and the decomposition predicate (`_stmt_has_any_suspension`) treats both
29
+ uniformly. The generator emitter that consumes YieldPayload is the next
30
+ migration step.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ from dataclasses import dataclass, field
35
+ from enum import Enum
36
+ from typing import TYPE_CHECKING, Union
37
+
38
+ from ..parse.nodes import (
39
+ TpyAssign, TpyAwait, TpyBreak, TpyContinue, TpyExceptHandler,
40
+ TpyExpr, TpyExprStmt, TpyForEach, TpyIf, TpyName,
41
+ TpyMatch,
42
+ TpyRaise, TpyReturn, TpyStmt, TpyTry, TpyVarDecl, TpyWhile, TpyWith,
43
+ TpyWithItem, TpyYield,
44
+ stmt_has_any_suspension as _stmt_has_any_suspension,
45
+ stmts_have_any_suspension as _stmts_have_any_suspension,
46
+ stmts_have_any_return as _stmts_have_any_return,
47
+ )
48
+
49
+ if TYPE_CHECKING:
50
+ from ..parse.nodes import TpyFunction
51
+ from .gen_generators import GeneratorForInfo
52
+
53
+
54
+ @dataclass
55
+ class ResumableFuncState:
56
+ """Per-function codegen state for the resumable-frame lowering, shared by
57
+ generators and `async def` coroutines. Attached to a `TpyFunction` as
58
+ `func._resumable_state` (via `resumable_state()`), built lazily by the
59
+ prescan / CFG-build / eligibility passes and consumed by the struct +
60
+ body emit.
61
+
62
+ Replaces ~16 individually string-keyed `getattr(func, "_resumable_*" /
63
+ "_async_*" / "_with_*")` side tables with one typed attribute: no
64
+ `getattr(..., None) or {}` ritual, no silent-None-on-typo miscompile, and
65
+ the prescan->emit contract (pass P populates fields A1..An) is visible in
66
+ one place. The `_async_*`-named members keep that prefix because they are
67
+ genuinely async-only (sub-coro struct names from `async for`/`async
68
+ with`, await-arg lifting); the rest are shape-neutral."""
69
+ # CFG build (_build_resumable_cfg)
70
+ cfg: 'CFG | None' = None
71
+ cfg_builder: 'CFGBuilder | None' = None
72
+ # await-arg lifting (_effective_body / _lift_nested_awaits)
73
+ lifted_body: 'list[TpyStmt] | None' = None
74
+ next_lift_id: int = 0
75
+ # for-loop prescan (_prescan_resumable_for_loops). `*_prescanned` flags
76
+ # preserve the original "cache even when the result is empty" semantics
77
+ # (an empty map distinct from "not yet prescanned").
78
+ for_prescanned: bool = False
79
+ for_uid_map: 'dict[int, int]' = field(default_factory=dict)
80
+ for_fields: 'list[tuple[str, str]]' = field(default_factory=list)
81
+ for_loop_info: 'dict[int, GeneratorForInfo]' = field(default_factory=dict)
82
+ for_info_by_uid: 'dict[int, GeneratorForInfo]' = field(default_factory=dict)
83
+ async_for_struct_names: 'dict[int, str]' = field(default_factory=dict)
84
+ # with-stmt prescan (_prescan_with_stmts)
85
+ with_prescanned: bool = False
86
+ with_uid_map: 'dict[int, list[int]]' = field(default_factory=dict)
87
+ with_fields: 'list[tuple[str, str]]' = field(default_factory=list)
88
+ with_owning_str_targets: 'set[str]' = field(default_factory=set)
89
+ async_with_struct_names: 'dict[int, tuple[str, str]]' = field(default_factory=dict)
90
+ # try/finally prescan (_prescan_resumable_try_finally)
91
+ try_finally_prescanned: bool = False
92
+ try_finally_uid_map: 'dict[int, int]' = field(default_factory=dict)
93
+ try_finally_fields: 'list[tuple[str, str]]' = field(default_factory=list)
94
+ # resumable-generator eligibility memoization (generator.py gate)
95
+ gen_eligible: 'bool | None' = None
96
+
97
+
98
+ def resumable_state(func: 'TpyFunction') -> ResumableFuncState:
99
+ """Get-or-create the `ResumableFuncState` attached to `func`."""
100
+ state = getattr(func, "_resumable_state", None)
101
+ if state is None:
102
+ state = ResumableFuncState()
103
+ func._resumable_state = state
104
+ return state
105
+
106
+
107
+ # -- Resumable-frame shape: which state machine the emitter produces.
108
+
109
+ class ResumableShape(Enum):
110
+ """Which resumable-frame shape the emitter (`gen_async.py`) produces.
111
+
112
+ ASYNC -- `async def`: `await` -> `__poll__(Waker) -> Poll<T>`.
113
+ GENERATOR -- generator: `yield` -> `__next__() -> expected<T,
114
+ StopIteration>`.
115
+ """
116
+ ASYNC = "async"
117
+ GENERATOR = "generator"
118
+
119
+
120
+ # -- AwaitPayload-specific enums (kept here so resumable_cfg owns the
121
+ # CFG-level metadata; gen_async re-imports them).
122
+
123
+ class AwaitMode(Enum):
124
+ """How an await transition stores its sub-future in the parent frame.
125
+
126
+ INLINE -- statically-known async def; sub-coro struct emplaced as a
127
+ sub-future field.
128
+ ERASED -- value awaitable (Task[T] / user value type implementing
129
+ Awaitable[T]); move-stored in an std::optional sub-future.
130
+ BORROWED -- reference-type awaitable (e.g. Future[T]); stored as a
131
+ raw pointer so observers see the same object across suspensions.
132
+ """
133
+ INLINE = "inline"
134
+ ERASED = "erased"
135
+ BORROWED = "borrowed"
136
+
137
+
138
+ class AwaitKind(Enum):
139
+ """How an await expression's result is consumed at its host stmt."""
140
+ ASSIGN = "assign"
141
+ VARDECL = "vardecl"
142
+ RETURN = "return"
143
+ DISCARD = "discard"
144
+
145
+
146
+ class AsyncWithKind(Enum):
147
+ """Which leg of an `async with` a synthetic yield emits.
148
+
149
+ M5's CFG synthesizes two Yield BBs per async-with: one for
150
+ `await __cm.__aenter__()` and one for `await __cm.__aexit__(...)`.
151
+ Emit dispatches on this enum rather than re-parsing the AST.
152
+ """
153
+ AENTER = "aenter"
154
+ AEXIT = "aexit"
155
+
156
+
157
+ # -- Suspension payloads (the shape-specific part of a yield terminator)
158
+
159
+ @dataclass(frozen=True)
160
+ class AwaitPayload:
161
+ """An async-def suspension. Held inside a Yield terminator."""
162
+ mode: AwaitMode
163
+ sub_field_cpp_type: str # cpp type for std::optional<...> / pointee
164
+ operand_expr: TpyExpr # the operand of `await ...`
165
+ kind: AwaitKind
166
+ bind_target: str | None # variable name for ASSIGN / VARDECL
167
+ return_stmt: TpyReturn | None # the original return for RETURN-kind
168
+ host_stmt: TpyStmt | None # source-level statement containing the await
169
+ # The TpyAwait node itself (sema attaches awaited_async_func_name /
170
+ # awaited_task_inner here; emit consults them).
171
+ await_node: TpyAwait
172
+ # Async-with internal yields: emit takes a special path that
173
+ # synthesizes `(*__with_ctx_<n>).__aenter__()` or
174
+ # `(*__with_ctx_<n>).__aexit__({}, nullptr, {})` directly rather
175
+ # than going through gen_expr on a synthesized AST. Set by
176
+ # `_build_async_with`; None for ordinary user awaits.
177
+ async_with_kind: 'AsyncWithKind | None' = None
178
+ async_with_ctx_n: int | None = None
179
+ # Set by `_build_async_for` so emit can synthesize
180
+ # `__sub_<i>.emplace(*__for_itr_<uid>)` and look up the sub-coro
181
+ # struct name via `func._async_for_struct_names`. None for
182
+ # ordinary user awaits.
183
+ async_for_uid: int | None = None
184
+
185
+
186
+ @dataclass(frozen=True)
187
+ class YieldPayload:
188
+ """A generator suspension. Held inside a Yield terminator when the CFG
189
+ is built from a generator body."""
190
+ value_expr: TpyExpr | None # the yielded expression (None for bare yield)
191
+ # The source `yield` statement, so emit can reuse the statement
192
+ # generator's `gen_yield_value` (storage->borrow bridging etc.).
193
+ yield_stmt: 'TpyYield | None' = None
194
+
195
+
196
+ SuspensionPayload = Union[AwaitPayload, YieldPayload]
197
+
198
+
199
+ # -- Regions: try/finally/with frames active at a BB
200
+
201
+ @dataclass(frozen=True)
202
+ class TryRegion:
203
+ """A try/except/finally frame active inside the try body.
204
+
205
+ `handlers` are the source-level except handlers; emit wraps the
206
+ case body in `catch (Type& e) { handler.body }` per handler.
207
+
208
+ Two finally shapes:
209
+ * Helper-based (`finally_helper_name` set): finally body has no
210
+ awaits and is emitted as a member function `void __finally_<n>()`
211
+ called on every throw / return / fall-through exit.
212
+ * CFG-based (`finally_entry_bb` / `captured_exc_field` set):
213
+ finally body has an await and lives in the main state machine.
214
+ On exception in the try body, the catch-all saves
215
+ `std::current_exception()` to the captured-exc frame field and
216
+ transitions state to `finally_entry_bb`. The finally body ends
217
+ with an `AsyncFinallyExit` synthetic stmt that rethrows the
218
+ saved exception (clearing the field) before falling through to
219
+ the post-try BB. Mutually exclusive with the helper-based shape.
220
+ """
221
+ handlers: tuple[TpyExceptHandler, ...]
222
+ finally_helper_name: str | None # None if try has no finally body OR CFG-based
223
+ tier: str # "throw" / "return" / "finally_only"
224
+ # Source-level loc for emit diagnostics.
225
+ loc_source: TpyStmt | None = None
226
+ # CFG-based finally fields (None for helper-based / no finally).
227
+ finally_entry_bb: int | None = None
228
+ captured_exc_field: str | None = None
229
+ # Pending-return slot for CFG-based finally: when set, a `return`
230
+ # inside the try body or any handler stores its value to
231
+ # `pending_return_slot` (when non-None -- void async defs leave it
232
+ # None) and sets `pending_return_flag = true`, then transitions
233
+ # state to the finally entry BB. AsyncFinallyExit checks the flag
234
+ # and emits the deferred Poll::ready after the rethrow check.
235
+ pending_return_flag: str | None = None
236
+ pending_return_slot: str | None = None
237
+
238
+
239
+ @dataclass(frozen=True)
240
+ class FinallyRegion:
241
+ """Marker for BBs inside a finally body. Emit treats specially:
242
+ a throw inside a finally re-raises *after* the body completes;
243
+ a `return` inside a finally body parks into the same pending-return
244
+ slot as a return from the try / handler body (`finally_exit_bb`
245
+ drains it via AsyncFinallyExit, Python: return-in-finally wins and
246
+ swallows any in-flight exception).
247
+ """
248
+ helper_name: str
249
+ loc_source: TpyStmt | None = None
250
+ # Parking fields mirror TryRegion's so a `return` inside the finally
251
+ # body sees the same slot as a return inside the try body. None when
252
+ # the try has no reachable returns anywhere in try / handlers /
253
+ # finally (no slot allocated).
254
+ pending_return_flag: str | None = None
255
+ pending_return_slot: str | None = None
256
+ # Dedicated BB whose single stmt is AsyncFinallyExit. Both normal
257
+ # fall-through and a return-in-finally jump here, so AsyncFinallyExit
258
+ # is the single replay site.
259
+ finally_exit_bb: int | None = None
260
+
261
+
262
+ @dataclass(frozen=True)
263
+ class ExceptRegion:
264
+ """Marker for BBs inside an except handler body. The corresponding
265
+ try frame is no longer on the stack (we've already caught the
266
+ exception). The sub-future reset happens in the C++ catch wrapper
267
+ emitted around the resume case body whose suspension threw -- so
268
+ the reset is per-yield, not per-handler.
269
+
270
+ `parent_finally` mirrors the enclosing TryRegion's
271
+ `finally_helper_name` so emit can run the finally body when control
272
+ leaves the handler normally (Python semantics: finally runs after
273
+ a matched except).
274
+
275
+ `parent_finally_entry_bb` is set instead when the parent try has
276
+ a CFG-based finally: an exception raised inside the handler body
277
+ must transition state to that BB (the inner try/catch in the
278
+ handler emit dispatches this via the TryRegion's
279
+ `captured_exc_field`). A `return` inside the handler body routes
280
+ through the same pending-return slot as a return in the try body.
281
+ """
282
+ handler: TpyExceptHandler
283
+ parent_finally: str | None = None
284
+ loc_source: TpyStmt | None = None
285
+ parent_finally_entry_bb: int | None = None
286
+ parent_pending_return_flag: str | None = None
287
+ parent_pending_return_slot: str | None = None
288
+
289
+
290
+ @dataclass(frozen=True)
291
+ class WithRegion:
292
+ """A with frame active inside the body. Emit wraps the case body in
293
+ a try/catch that runs __exit__ with the exception args and either
294
+ suppresses or re-raises depending on __exit__'s return.
295
+
296
+ `ctx_n` is the uid used to name both the `__with_ctx_<n>` frame
297
+ slot (where the context-manager object lives, as
298
+ `std::optional<T>`) and the `__exc_<n>` BaseException& binding
299
+ inside the catch wrap. `post_with_bb` is the CFG BB to transition
300
+ to when __exit__ suppresses an exception (only meaningful when
301
+ item.exit_can_suppress).
302
+ """
303
+ item: TpyWithItem
304
+ ctx_n: int
305
+ post_with_bb: int
306
+ loc_source: TpyStmt | None = None
307
+
308
+
309
+ Region = Union[TryRegion, FinallyRegion, ExceptRegion, WithRegion]
310
+
311
+
312
+ # -- Terminators: one per BB
313
+
314
+ @dataclass(frozen=True)
315
+ class Fall:
316
+ """Fall through to next_bb. Used for straight-line edges and
317
+ explicit break/continue (which target loop exit / loop top)."""
318
+ next_bb: int
319
+
320
+
321
+ @dataclass(frozen=True)
322
+ class Branch:
323
+ """Conditional branch on `cond`."""
324
+ cond: TpyExpr
325
+ then_bb: int
326
+ else_bb: int
327
+
328
+
329
+ @dataclass(frozen=True)
330
+ class Yield:
331
+ """Suspension point. Saves state, returns Pending (async) / yield
332
+ value (generator); resume_bb is the entry point on resume."""
333
+ payload: SuspensionPayload
334
+ resume_bb: int
335
+ suspension_index: int # bijective with resume_bb among yields
336
+
337
+
338
+ @dataclass(frozen=True)
339
+ class ReturnT:
340
+ """Return from the function. Emit walks the active finally chain
341
+ before emitting the actual return."""
342
+ value: TpyExpr | None
343
+ return_stmt: TpyReturn # the original statement (for type info)
344
+
345
+
346
+ @dataclass(frozen=True)
347
+ class RaiseT:
348
+ """Raise an exception. Emit walks the active finally chain via
349
+ the exception unwinding mechanism (no explicit chain run)."""
350
+ raise_stmt: TpyRaise
351
+
352
+
353
+ @dataclass(frozen=True)
354
+ class Unreachable:
355
+ """BB whose end is statically unreachable (e.g. infinite loop body
356
+ with no break/return)."""
357
+ pass
358
+
359
+
360
+ @dataclass(frozen=True)
361
+ class AsyncForAdvance:
362
+ """Terminator for the cond/advance BB of a CFG-decomposed for-loop
363
+ whose body contains await. Emit:
364
+ __for_r_<uid> = (*__for_itr_<uid>).__next__();
365
+ if (!(*__for_r_<uid>).has_value()) -> exhausted_bb
366
+ <loop_var> = ::tpy::unwrap_ref(*(*__for_r_<uid>));
367
+ -> has_value_bb
368
+ Two successors mirror Branch so case-entries pred-counting works."""
369
+ uid: int
370
+ stmt: TpyForEach
371
+ has_value_bb: int
372
+ exhausted_bb: int
373
+
374
+
375
+ @dataclass(frozen=True)
376
+ class MatchDispatch:
377
+ """Terminator for a `match` whose arm bodies contain a suspension (H1).
378
+
379
+ The suspension-free dispatch (subject eval + every arm test + bindings
380
+ + guards) is emitted by reusing the ordinary `gen_match`; each arm
381
+ *body* is routed back through the resumable walker (it may suspend),
382
+ so arm bodies live in the state machine while the dispatch keeps its
383
+ type-aware switch / if-elif shape. `arm_bbs[i]` is the entry BB of
384
+ `match_stmt.cases[i].body`; `join_bb` is the post-match continuation
385
+ (None when the match is exhaustive and every arm terminates, so no
386
+ fall-through exists)."""
387
+ match_stmt: TpyMatch
388
+ arm_bbs: tuple[int, ...]
389
+ join_bb: int | None
390
+
391
+
392
+ Terminator = Union[Fall, Branch, Yield, ReturnT, RaiseT, Unreachable,
393
+ AsyncForAdvance, MatchDispatch]
394
+
395
+
396
+ # -- Synthetic leaf-stmt types for CFG-lowered for-loops ----------------
397
+
398
+ @dataclass(frozen=True)
399
+ class AsyncForIterSetup:
400
+ """Synthetic leaf stmt at the head of a CFG-decomposed for-loop.
401
+
402
+ Sync (`is_async=False`, v1.5 M3.1) emits:
403
+ `__for_itr_<uid> = ::tpy::__iter__(<iterable_expr>);`
404
+ Async (`is_async=True`, v1.5 M6) emits:
405
+ `__for_itr_<uid> = (<iterable_expr>).__aiter__();`
406
+ The frame field name is shared since only one of the two paths is
407
+ active per uid."""
408
+ uid: int
409
+ iterable_expr: TpyExpr
410
+ is_async: bool = False
411
+ # Loop element type, used by the range strategy's counter init (the
412
+ # emitter needs the C++ element type for the counter/stop casts).
413
+ elem_type: 'TpyType | None' = None
414
+
415
+
416
+ @dataclass(frozen=True)
417
+ class AsyncFinallyExit:
418
+ """Synthetic leaf stmt at the single replay site of a CFG-decomposed
419
+ finally region (allocated as the finally's dedicated `finally_exit_bb`
420
+ so both normal fall-through and a return-in-finally drain through one
421
+ point). Two-stage emit -- pending-return check FIRST so a return in
422
+ the finally body wins over any in-flight exception (Python
423
+ semantics):
424
+
425
+ if (this-><pending_return_flag>) {
426
+ this-><pending_return_flag> = false;
427
+ this-><captured_exc_field> = nullptr; // swallow in-flight exc
428
+ <walk outer finally chain>
429
+ __state = S_DONE;
430
+ return Poll<T>::ready(std::move(this-><pending_return_slot>));
431
+ }
432
+ if (this-><captured_exc_field>) {
433
+ std::exception_ptr __tmp = this-><captured_exc_field>;
434
+ this-><captured_exc_field> = nullptr;
435
+ std::rethrow_exception(__tmp);
436
+ }
437
+
438
+ Stage 1 fires when a `return` originating in the try body, any
439
+ handler body, OR the finally body itself set the pending flag. It
440
+ has two shapes depending on whether an enclosing CFG-finally region
441
+ is active at this exit site (see `_emit_async_finally_exit`):
442
+ * No enclosing CFG finally: replay locally -- walk the outer
443
+ helper chain, then emit Poll<T>::ready / StopIteration as shown
444
+ above (the deferred return is delivered as the coro's exit).
445
+ * Enclosing CFG finally present: forward -- move this exit's
446
+ parked value into the outer's `pending_return_slot`, set the
447
+ outer's `pending_return_flag`, walk helpers between this exit
448
+ and the outer's finally entry, then transition state to the
449
+ outer's `finally_entry_bb`. The outer's AsyncFinallyExit
450
+ eventually performs the local replay (or forwards again, for
451
+ deeper nesting).
452
+ The captured-exc clear-on-its-way-out implements the Python
453
+ return-in-finally-wins-over-raise rule. Stage 2 rethrows a saved
454
+ in-flight exception when no return is pending. Fields are cleared
455
+ after extraction so a re-entry to the same try (e.g. inside a loop)
456
+ doesn't carry over stale state.
457
+
458
+ `pending_return_flag` / `pending_return_slot` are None when no
459
+ `return` is reachable in try / handlers / finally (stage 1 omitted).
460
+ `pending_return_slot` is None for void async defs even when the flag
461
+ is set (the deferred return needs no value)."""
462
+ captured_exc_field: str
463
+ pending_return_flag: str | None = None
464
+ pending_return_slot: str | None = None
465
+
466
+
467
+ @dataclass(frozen=True)
468
+ class WithEnter:
469
+ """Synthetic leaf stmt at the head of a CFG-decomposed sync `with`
470
+ body (sync `with X:` compiled inside an `async def`, M3.2). Real
471
+ Python `async with` uses its own setup synthetic (see AsyncWithSetup).
472
+ Emit:
473
+ __with_ctx_<n> = <context_expr>;
474
+ (*__with_ctx_<n>).__enter__(); # if target is None
475
+ <target> = (*__with_ctx_<n>).__enter__(); # otherwise
476
+ """
477
+ ctx_n: int
478
+ item: TpyWithItem
479
+
480
+
481
+ @dataclass(frozen=True)
482
+ class AsyncWithSetup:
483
+ """Synthetic leaf stmt at the head of a CFG-decomposed `async with`
484
+ region (v1.5 M5). Stores the context manager in a frame slot so the
485
+ subsequent `__aenter__` / `__aexit__` yields can dispatch through it.
486
+
487
+ Emit:
488
+ __with_ctx_<n> = <context_expr>;
489
+
490
+ (`__with_ctx_<n>` is the frame-hoisted `std::optional<CM>` field
491
+ allocated by the prescan; assigning a CM rvalue constructs in
492
+ place via `operator=`.) The Yield site immediately following
493
+ emplaces `__sub_<i>` with `(*__with_ctx_<n>, ...)` for the
494
+ `__aenter__` call; the finally body's Yield does the same for
495
+ `__aexit__(None, None, None)`.
496
+ """
497
+ ctx_n: int
498
+ item: TpyWithItem
499
+
500
+
501
+ # -- Basic block
502
+
503
+ @dataclass
504
+ class BB:
505
+ """A basic block.
506
+
507
+ `stmts` are leaf statements emitted via the normal StatementGenerator;
508
+ compound statements that *don't* transitively contain a suspension
509
+ stay as single elements here (lazy decomposition). Compound
510
+ statements that *do* contain suspensions are decomposed during CFG
511
+ construction and never appear in `stmts`.
512
+
513
+ `region_stack` is the (outermost ... innermost) list of regions
514
+ active when control enters this BB. Emit wraps the BB's emission
515
+ in the region_stack reconstructed as nested C++ try blocks.
516
+
517
+ `region_stack` is mutable (set by the builder's annotation pass).
518
+ """
519
+ id: int
520
+ stmts: list[TpyStmt] = field(default_factory=list)
521
+ terminator: Terminator | None = None
522
+ region_stack: tuple[Region, ...] = ()
523
+ # Set by the (future) reachability pass; True iff this BB is reached
524
+ # only as the resume entry of a Yield (i.e. is a state target). Used
525
+ # by emit to decide whether to emit a case label.
526
+ is_resume_entry: bool = False
527
+
528
+
529
+ # -- Loop-context tracking (used during construction; not in the final CFG)
530
+
531
+ @dataclass
532
+ class _LoopCtx:
533
+ """Pushed onto a stack while building loop bodies so break/continue
534
+ can resolve to the correct target BB.
535
+
536
+ `continue_bb` is the loop's check/step BB (for while: the condition
537
+ check; for for: the iterator advance). `break_bb` is the BB the
538
+ loop exits to. `regions_at_entry` is the region stack snapshot at
539
+ loop entry, used to compute the region delta on break/continue.
540
+ """
541
+ continue_bb: int
542
+ break_bb: int
543
+ regions_at_entry: tuple[Region, ...]
544
+
545
+
546
+ # -- The CFG container
547
+
548
+ @dataclass
549
+ class CFG:
550
+ entry_bb: int
551
+ blocks: dict[int, BB]
552
+ yield_sites: list[Yield] = field(default_factory=list)
553
+ # Finally-body helper functions to emit as private members. Each
554
+ # entry is (helper_name, body_stmts). Order matters for stable
555
+ # output.
556
+ finally_helpers: list[tuple[str, list[TpyStmt]]] = field(default_factory=list)
557
+ # Cached emit-side maps. Populated lazily by the consumer (e.g.
558
+ # AsyncCoroCodegen) on first use; reused across struct-emit + poll-
559
+ # emit passes so we don't pay the O(N) predecessor walk + region-
560
+ # stack equality scan twice per function. The case-entries value
561
+ # type is consumer-defined (async emits StateLabel; future generator
562
+ # port may use a different shape) -- typed `dict[int, object]` to
563
+ # keep this module shape-neutral.
564
+ _case_entries_cache: dict[int, object] | None = field(default=None, repr=False)
565
+ _resume_to_yield_cache: dict[int, Yield] | None = field(default=None, repr=False)
566
+
567
+ def bb(self, bb_id: int) -> BB:
568
+ return self.blocks[bb_id]
569
+
570
+ def resume_to_yield(self) -> dict[int, Yield]:
571
+ """`resume_bb -> Yield` map, built once and cached."""
572
+ if self._resume_to_yield_cache is None:
573
+ self._resume_to_yield_cache = {
574
+ y.resume_bb: y for y in self.yield_sites
575
+ }
576
+ return self._resume_to_yield_cache
577
+
578
+
579
+ # -- Builder
580
+
581
+ class CFGBuilder:
582
+ """Walks a TpyFunction body and produces a CFG.
583
+
584
+ Usage:
585
+ builder = CFGBuilder()
586
+ cfg = builder.build(func_body)
587
+
588
+ The builder maintains a "current BB" while walking statements.
589
+ Compound statements that contain suspensions branch the CFG;
590
+ statements that don't are appended to the current BB as leaf
591
+ statements.
592
+ """
593
+
594
+ def __init__(self, payload_factory=None,
595
+ for_uid_map: 'dict[int, int] | None' = None,
596
+ with_uid_map: 'dict[int, list[int]] | None' = None,
597
+ try_finally_uid_map:
598
+ 'dict[int, int] | None' = None,
599
+ func_returns_void: bool = False) -> None:
600
+ """
601
+ `payload_factory(await_node, host_stmt, kind, bind_target,
602
+ return_stmt) -> AwaitPayload` is called for each top-level await
603
+ the builder encounters. The factory fills in mode +
604
+ sub_field_cpp_type, which are async-specific. If None, a
605
+ placeholder payload is created (suitable for testing only).
606
+
607
+ `for_uid_map` maps id(TpyForEach) -> uid for for-loops that the
608
+ caller has pre-scanned and registered with frame fields. Used by
609
+ `_build_for` to attach the correct uid to AsyncForIterSetup /
610
+ AsyncForAdvance. Missing entries cause `_build_for` to raise
611
+ _CFGNotYetSupported (the caller is expected to pre-register every
612
+ for-with-await loop).
613
+
614
+ `with_uid_map` maps id(TpyWith) -> list of ctx_n (one per WithItem,
615
+ in source order) for with-stmts pre-scanned and registered. Used
616
+ by `_build_with`. Missing entries cause `_build_with` to raise.
617
+ """
618
+ self._blocks: dict[int, BB] = {}
619
+ self._next_bb_id: int = 0
620
+ self._yield_sites: list[Yield] = []
621
+ self._finally_helpers: list[tuple[str, list[TpyStmt]]] = []
622
+ self._next_finally_id: int = 0
623
+ # Construction-time stacks.
624
+ self._loop_stack: list[_LoopCtx] = []
625
+ self._region_stack: list[Region] = []
626
+ self._payload_factory = payload_factory
627
+ self._for_uid_map: dict[int, int] = for_uid_map or {}
628
+ self._with_uid_map: dict[int, list[int]] = with_uid_map or {}
629
+ self._try_finally_uid_map: dict[int, int] = try_finally_uid_map or {}
630
+ self._func_returns_void: bool = func_returns_void
631
+ # (id(region), id(handler)) -> handler-entry BB id. Populated
632
+ # by `_record_handler_entry` during try/except construction;
633
+ # read by emitters via `get_handler_entry`.
634
+ self._handler_entries: dict[tuple[int, int], int] = {}
635
+
636
+ # -- Public entry point ---------------------------------------------
637
+
638
+ def build(self, body: list[TpyStmt]) -> CFG:
639
+ """Build a CFG from a resumable function body (async def or
640
+ generator). Shape-neutral: a `yield` is a suspension point
641
+ alongside `await`."""
642
+ entry = self._new_bb()
643
+ end = self._build_block(entry, body)
644
+ # If the body fell off the end without a terminator, mark the
645
+ # tail BB with Unreachable. The emitter inserts the appropriate
646
+ # "fell-through-without-returning" handling (Ready(unit) for
647
+ # `-> None`, panic otherwise).
648
+ if end is not None and self._blocks[end].terminator is None:
649
+ self._blocks[end].terminator = Unreachable()
650
+ cfg = CFG(
651
+ entry_bb=entry,
652
+ blocks=self._blocks,
653
+ yield_sites=self._yield_sites,
654
+ finally_helpers=self._finally_helpers,
655
+ )
656
+ self._mark_resume_entries(cfg)
657
+ return cfg
658
+
659
+ # -- BB helpers -----------------------------------------------------
660
+
661
+ def _new_bb(self) -> int:
662
+ bb_id = self._next_bb_id
663
+ self._next_bb_id += 1
664
+ bb = BB(id=bb_id, region_stack=tuple(self._region_stack))
665
+ self._blocks[bb_id] = bb
666
+ return bb_id
667
+
668
+ def _finish(self, bb_id: int, terminator: Terminator) -> None:
669
+ """Set the terminator on a BB if it doesn't already have one.
670
+ A BB ending in return/raise/yield/branch is sealed; subsequent
671
+ statements in the source belong to a fresh BB."""
672
+ bb = self._blocks[bb_id]
673
+ if bb.terminator is None:
674
+ bb.terminator = terminator
675
+
676
+ def _is_sealed(self, bb_id: int) -> bool:
677
+ return self._blocks[bb_id].terminator is not None
678
+
679
+ # -- Body walker ----------------------------------------------------
680
+
681
+ def _build_block(self, entry_bb: int, stmts: list[TpyStmt]) -> int | None:
682
+ """Walk a statement list, appending to / forking from entry_bb.
683
+ Returns the BB id where control flows out (None if the block
684
+ terminates unconditionally via return/raise/break/continue)."""
685
+ cur = entry_bb
686
+ for stmt in stmts:
687
+ if self._is_sealed(cur):
688
+ # Source-level dead code after a return/raise/break/
689
+ # continue. Emit into a fresh BB that's only reachable
690
+ # from nothing; it'll be DCE'd by the emitter.
691
+ cur = self._new_bb()
692
+ cur_after = self._build_stmt(cur, stmt)
693
+ if cur_after is None:
694
+ return None
695
+ cur = cur_after
696
+ return cur
697
+
698
+ def _build_stmt(self, cur: int, stmt: TpyStmt) -> int | None:
699
+ """Process one statement. Returns the BB id where control flows
700
+ out of this statement (None for unconditional terminators)."""
701
+ # Statements with explicit terminator semantics.
702
+ if isinstance(stmt, TpyReturn):
703
+ return self._build_return(cur, stmt)
704
+ if isinstance(stmt, TpyRaise):
705
+ self._finish(cur, RaiseT(raise_stmt=stmt))
706
+ return None
707
+ if isinstance(stmt, TpyBreak):
708
+ self._build_break(cur)
709
+ return None
710
+ if isinstance(stmt, TpyContinue):
711
+ self._build_continue(cur)
712
+ return None
713
+ # Statements that may host a top-level await.
714
+ if self._stmt_has_top_level_await(stmt):
715
+ return self._build_top_level_await_stmt(cur, stmt)
716
+ # A `yield` is a generator suspension at statement position.
717
+ if isinstance(stmt, TpyYield):
718
+ return self._build_yield_stmt(cur, stmt)
719
+ # A non-loop compound (if/try/with) inside a decomposed loop
720
+ # must be decomposed if it contains break/continue targeting
721
+ # the outer loop -- otherwise the break/continue would become
722
+ # C++ break/continue inside our state-machine switch instead
723
+ # of a CFG state transition.
724
+ force_loop_decomp = (self._loop_stack
725
+ and _stmt_has_unbound_loop_transfer(stmt))
726
+ # Compound statements: decompose if they contain a nested
727
+ # suspension OR an unbound break/continue inside a loop.
728
+ if isinstance(stmt, TpyIf):
729
+ if _stmt_has_any_suspension(stmt) or force_loop_decomp:
730
+ return self._build_if(cur, stmt)
731
+ elif isinstance(stmt, TpyWhile):
732
+ if _stmt_has_any_suspension(stmt):
733
+ return self._build_while(cur, stmt)
734
+ elif isinstance(stmt, TpyForEach):
735
+ if stmt.is_async or _stmt_has_any_suspension(stmt):
736
+ return self._build_for(cur, stmt)
737
+ elif isinstance(stmt, TpyTry):
738
+ if _stmt_has_any_suspension(stmt) or force_loop_decomp:
739
+ return self._build_try(cur, stmt)
740
+ elif isinstance(stmt, TpyWith):
741
+ if _stmt_has_any_suspension(stmt) or force_loop_decomp:
742
+ return self._build_with(cur, stmt)
743
+ elif isinstance(stmt, TpyMatch):
744
+ if _stmt_has_any_suspension(stmt) or force_loop_decomp:
745
+ return self._build_match(cur, stmt)
746
+ # Leaf statement (or compound without suspensions). If a statement
747
+ # reaches this point STILL containing a suspension, it's a statement
748
+ # kind the builder doesn't decompose (if/while/for/try/with/match all
749
+ # are). Appending it as a leaf would emit the nested `await`/`yield`
750
+ # as straight-line code -- silently wrong for async, and for a
751
+ # generator it would collide with the resumable state enum. Refuse so
752
+ # the build surfaces a clean CodeGenError instead of a miscompile.
753
+ if _stmt_has_any_suspension(stmt):
754
+ raise _CFGNotYetSupported(
755
+ "a suspension (`await`/`yield`) inside this statement is "
756
+ "not yet supported by the resumable lowering (the statement "
757
+ "kind is not decomposed by the CFG builder).",
758
+ loc=getattr(stmt, "loc", None),
759
+ )
760
+ self._blocks[cur].stmts.append(stmt)
761
+ return cur
762
+
763
+ # -- Top-level await on assign/vardecl/return/expr-stmt -------------
764
+
765
+ def _stmt_has_top_level_await(self, stmt: TpyStmt) -> bool:
766
+ return _top_level_await_in(stmt) is not None
767
+
768
+ def _build_top_level_await_stmt(self, cur: int, stmt: TpyStmt) -> int | None:
769
+ """Convert a statement whose top-level expression slot is a
770
+ TpyAwait into a Yield terminator. The bind / discard / return
771
+ action runs in the resume BB."""
772
+ await_node = _top_level_await_in(stmt)
773
+ assert await_node is not None
774
+ kind, bind_target, return_stmt = _classify_await_position(stmt, await_node)
775
+ if self._payload_factory is not None:
776
+ payload = self._payload_factory(
777
+ await_node, stmt, kind, bind_target, return_stmt)
778
+ else:
779
+ payload = AwaitPayload(
780
+ mode=AwaitMode.INLINE,
781
+ sub_field_cpp_type="",
782
+ operand_expr=await_node.value,
783
+ kind=kind,
784
+ bind_target=bind_target,
785
+ return_stmt=return_stmt,
786
+ host_stmt=stmt,
787
+ await_node=await_node,
788
+ )
789
+ suspension_idx = len(self._yield_sites)
790
+ resume_bb = self._new_bb()
791
+ terminator = Yield(
792
+ payload=payload,
793
+ resume_bb=resume_bb,
794
+ suspension_index=suspension_idx,
795
+ )
796
+ self._finish(cur, terminator)
797
+ self._yield_sites.append(terminator)
798
+ # If the await's host stmt was a TpyReturn, the resume case
799
+ # body runs the finally chain and returns; control does not
800
+ # flow past it.
801
+ if kind is AwaitKind.RETURN:
802
+ self._finish(resume_bb, ReturnT(value=None, return_stmt=return_stmt))
803
+ return None
804
+ return resume_bb
805
+
806
+ def _build_yield_stmt(self, cur: int, stmt: TpyYield) -> int | None:
807
+ """Convert a `yield` statement into a Yield terminator. Unlike an
808
+ `await`, a yield pushes its value OUT to the caller and resumes
809
+ in place -- there is no sub-future to poll and (until `send()`)
810
+ no value bound back in, so the resume BB simply continues after
811
+ the yield. The generator emitter reads `value_expr` / `yield_stmt`
812
+ off the payload to produce the yielded value."""
813
+ payload = YieldPayload(value_expr=stmt.value, yield_stmt=stmt)
814
+ suspension_idx = len(self._yield_sites)
815
+ resume_bb = self._new_bb()
816
+ terminator = Yield(
817
+ payload=payload,
818
+ resume_bb=resume_bb,
819
+ suspension_index=suspension_idx,
820
+ )
821
+ self._finish(cur, terminator)
822
+ self._yield_sites.append(terminator)
823
+ return resume_bb
824
+
825
+ def _build_return(self, cur: int, stmt: TpyReturn) -> int | None:
826
+ # `return await ...` is the RETURN await kind; routed through
827
+ # _build_top_level_await_stmt.
828
+ if isinstance(stmt.value, TpyAwait):
829
+ return self._build_top_level_await_stmt(cur, stmt)
830
+ self._finish(cur, ReturnT(value=stmt.value, return_stmt=stmt))
831
+ return None
832
+
833
+ # -- break / continue -----------------------------------------------
834
+
835
+ def _build_break(self, cur: int) -> None:
836
+ if not self._loop_stack:
837
+ # Sema would have rejected; treat as unreachable.
838
+ self._finish(cur, Unreachable())
839
+ return
840
+ target = self._loop_stack[-1].break_bb
841
+ self._finish(cur, Fall(next_bb=target))
842
+
843
+ def _build_continue(self, cur: int) -> None:
844
+ if not self._loop_stack:
845
+ self._finish(cur, Unreachable())
846
+ return
847
+ target = self._loop_stack[-1].continue_bb
848
+ self._finish(cur, Fall(next_bb=target))
849
+
850
+ # -- if/else --------------------------------------------------------
851
+
852
+ def _build_if(self, cur: int, stmt: TpyIf) -> int | None:
853
+ then_bb = self._new_bb()
854
+ else_bb = self._new_bb()
855
+ join_bb = self._new_bb()
856
+ self._finish(cur, Branch(cond=stmt.condition, then_bb=then_bb,
857
+ else_bb=else_bb))
858
+ then_end = self._build_block(then_bb, stmt.then_body)
859
+ if then_end is not None:
860
+ self._finish(then_end, Fall(next_bb=join_bb))
861
+ else_end = self._build_block(else_bb, stmt.else_body)
862
+ if else_end is not None:
863
+ self._finish(else_end, Fall(next_bb=join_bb))
864
+ # If both branches terminate, the join is unreachable.
865
+ if then_end is None and else_end is None:
866
+ return None
867
+ return join_bb
868
+
869
+ # -- while ----------------------------------------------------------
870
+
871
+ def _build_while(self, cur: int, stmt: TpyWhile) -> int | None:
872
+ check_bb = self._new_bb()
873
+ body_bb = self._new_bb()
874
+ # `exit_bb` is the NORMAL-exit target (condition false). When there
875
+ # is an `else` clause it runs there; `break` instead targets a
876
+ # separate `after_bb` that skips the else (Python loop-else
877
+ # semantics). With no else, `after_bb` IS `exit_bb` (no extra BB,
878
+ # so non-else loops are structurally unchanged).
879
+ exit_bb = self._new_bb()
880
+ self._finish(cur, Fall(next_bb=check_bb))
881
+ self._finish(check_bb, Branch(cond=stmt.condition, then_bb=body_bb,
882
+ else_bb=exit_bb))
883
+ after_bb = self._new_bb() if stmt.orelse else exit_bb
884
+ self._loop_stack.append(_LoopCtx(
885
+ continue_bb=check_bb,
886
+ break_bb=after_bb,
887
+ regions_at_entry=tuple(self._region_stack),
888
+ ))
889
+ try:
890
+ body_end = self._build_block(body_bb, stmt.body)
891
+ if body_end is not None:
892
+ self._finish(body_end, Fall(next_bb=check_bb))
893
+ finally:
894
+ self._loop_stack.pop()
895
+ if stmt.orelse:
896
+ # else runs on the normal-exit edge, then falls to after_bb;
897
+ # break edges (-> after_bb) skip it. The else body is a regular
898
+ # region so it may itself contain suspensions.
899
+ else_end = self._build_block(exit_bb, stmt.orelse)
900
+ if else_end is not None:
901
+ self._finish(else_end, Fall(next_bb=after_bb))
902
+ return after_bb
903
+ return exit_bb
904
+
905
+ # -- for ------------------------------------------------------------
906
+
907
+ def _build_for(self, cur: int, stmt: TpyForEach) -> int | None:
908
+ if stmt.is_async:
909
+ return self._build_async_for(cur, stmt)
910
+ # Universal iter/next lowering. The for-loop becomes:
911
+ # iter_init -> cond_advance --(has_value)-> body BBs --(fall)-> cond_advance
912
+ # --(exhausted)-> exit
913
+ # The cond/advance BB carries an AsyncForAdvance terminator
914
+ # which expands at emit to next() + has_value check + bind.
915
+ uid = self._for_uid_map.get(id(stmt))
916
+ if uid is None:
917
+ raise _CFGNotYetSupported(
918
+ "for-loop with await reached CFG builder without a "
919
+ "registered uid (internal: pre-scan missed this loop).",
920
+ loc=stmt.loc,
921
+ )
922
+ iter_init_bb = self._new_bb()
923
+ cond_bb = self._new_bb()
924
+ body_bb = self._new_bb()
925
+ # `exit_bb` is the EXHAUSTED (normal-exit) target; the `else` clause
926
+ # runs there. `break` targets `after_bb` (skips else). No else ->
927
+ # after_bb IS exit_bb (non-else for-loops unchanged).
928
+ exit_bb = self._new_bb()
929
+ # Fall into iter init.
930
+ self._finish(cur, Fall(next_bb=iter_init_bb))
931
+ # iter_init_bb: setup, fall to cond.
932
+ self._blocks[iter_init_bb].stmts.append(
933
+ AsyncForIterSetup(uid=uid, iterable_expr=stmt.iterable,
934
+ elem_type=stmt.elem_type))
935
+ self._finish(iter_init_bb, Fall(next_bb=cond_bb))
936
+ # cond_bb: AsyncForAdvance terminator (advance + branch).
937
+ self._finish(cond_bb, AsyncForAdvance(
938
+ uid=uid, stmt=stmt,
939
+ has_value_bb=body_bb, exhausted_bb=exit_bb))
940
+ after_bb = self._new_bb() if stmt.orelse else exit_bb
941
+ # body: continue=cond_bb, break=after_bb (skips the else).
942
+ self._loop_stack.append(_LoopCtx(
943
+ continue_bb=cond_bb,
944
+ break_bb=after_bb,
945
+ regions_at_entry=tuple(self._region_stack),
946
+ ))
947
+ try:
948
+ body_end = self._build_block(body_bb, stmt.body)
949
+ if body_end is not None:
950
+ self._finish(body_end, Fall(next_bb=cond_bb))
951
+ finally:
952
+ self._loop_stack.pop()
953
+ if stmt.orelse:
954
+ else_end = self._build_block(exit_bb, stmt.orelse)
955
+ if else_end is not None:
956
+ self._finish(else_end, Fall(next_bb=after_bb))
957
+ return after_bb
958
+ return exit_bb
959
+
960
+ def _build_async_for(self, cur: int, stmt: TpyForEach) -> int | None:
961
+ """Lower `async for y in ait: <body>` (v1.5 M6).
962
+
963
+ Shape:
964
+ iter_init_bb (AsyncForIterSetup(is_async=True))
965
+ -> TryRegion[ExceptRegion(StopAsyncIteration -> break)] {
966
+ cond_bb: Yield(await __aiter.__anext__(), bind y)
967
+ resume_bb: (binds y, falls out of try)
968
+ }
969
+ (pop TryRegion)
970
+ body_bb: <user body>, Fall -> cond_bb
971
+ handler -> exit_bb (via the loop's break_bb)
972
+
973
+ The TryRegion wraps ONLY the cond/resume pair, NOT the body.
974
+ A StopAsyncIteration from the body must propagate up like any
975
+ other exception, so the body's case bodies must not be wrapped
976
+ in the auto-catch.
977
+
978
+ Parser rejects `else:` on async-for, so no orelse handling here.
979
+ """
980
+ uid = self._for_uid_map.get(id(stmt))
981
+ if uid is None:
982
+ raise _CFGNotYetSupported(
983
+ "async-for reached CFG builder without a registered "
984
+ "uid (internal: pre-scan missed this loop).",
985
+ loc=stmt.loc,
986
+ )
987
+ # body_bb / exit_bb are created OUTSIDE the TryRegion push so a
988
+ # StopAsyncIteration thrown from the body propagates rather than
989
+ # being silently caught by the loop's auto-handler. iter_init_bb
990
+ # likewise stays outside (the __aiter__ call shouldn't throw
991
+ # StopAsyncIteration, and a try region around it would distort
992
+ # the case structure). cond_bb / resume_bb / handler body BB are
993
+ # created below INSIDE the push so the emitter wraps their case
994
+ # bodies in `catch (StopAsyncIteration&)`.
995
+ iter_init_bb = self._new_bb()
996
+ body_bb = self._new_bb()
997
+ exit_bb = self._new_bb()
998
+
999
+ self._finish(cur, Fall(next_bb=iter_init_bb))
1000
+ self._blocks[iter_init_bb].stmts.append(
1001
+ AsyncForIterSetup(uid=uid, iterable_expr=stmt.iterable,
1002
+ is_async=True))
1003
+
1004
+ # The handler body is a synthesized TpyBreak -- _build_block
1005
+ # routes it through _loop_stack to the loop's break_bb, so
1006
+ # _loop_stack must be active while the handler is built.
1007
+ handler = TpyExceptHandler(
1008
+ exception_type="StopAsyncIteration",
1009
+ binding=None,
1010
+ body=[TpyBreak(loc=stmt.loc)],
1011
+ loc=stmt.loc,
1012
+ )
1013
+ try_region = TryRegion(
1014
+ handlers=(handler,),
1015
+ finally_helper_name=None,
1016
+ tier="throw",
1017
+ loc_source=stmt,
1018
+ )
1019
+
1020
+ self._region_stack.append(try_region)
1021
+ cond_bb = self._new_bb()
1022
+ resume_bb = self._new_bb()
1023
+ self._loop_stack.append(_LoopCtx(
1024
+ continue_bb=cond_bb,
1025
+ break_bb=exit_bb,
1026
+ regions_at_entry=tuple(self._region_stack),
1027
+ ))
1028
+ try:
1029
+ self._finish(iter_init_bb, Fall(next_bb=cond_bb))
1030
+ dummy_await = TpyAwait(value=stmt.iterable, loc=stmt.loc)
1031
+ payload = AwaitPayload(
1032
+ mode=AwaitMode.INLINE,
1033
+ sub_field_cpp_type="", # filled in by struct emit
1034
+ operand_expr=stmt.iterable,
1035
+ kind=AwaitKind.ASSIGN,
1036
+ bind_target=stmt.var,
1037
+ return_stmt=None,
1038
+ host_stmt=stmt,
1039
+ await_node=dummy_await,
1040
+ async_for_uid=uid,
1041
+ )
1042
+ yield_term = Yield(
1043
+ payload=payload,
1044
+ resume_bb=resume_bb,
1045
+ suspension_index=len(self._yield_sites),
1046
+ )
1047
+ self._finish(cond_bb, yield_term)
1048
+ self._yield_sites.append(yield_term)
1049
+ # Build the synthesized except handler body (single TpyBreak)
1050
+ # while the TryRegion is still on the stack so the
1051
+ # ExceptRegion has the right parent.
1052
+ except_region = ExceptRegion(
1053
+ handler=handler,
1054
+ parent_finally=None,
1055
+ loc_source=stmt,
1056
+ )
1057
+ self._region_stack.append(except_region)
1058
+ try:
1059
+ handler_entry = self._new_bb()
1060
+ handler_end = self._build_block(handler_entry, handler.body)
1061
+ assert handler_end is None # TpyBreak terminates
1062
+ self._record_handler_entry(try_region, handler, handler_entry)
1063
+ finally:
1064
+ self._region_stack.pop()
1065
+ finally:
1066
+ self._region_stack.pop()
1067
+
1068
+ # body_bb sits outside the TryRegion -- a user `await` in the
1069
+ # body whose result throws StopAsyncIteration propagates up
1070
+ # rather than being silently swallowed.
1071
+ self._finish(resume_bb, Fall(next_bb=body_bb))
1072
+ try:
1073
+ body_end = self._build_block(body_bb, stmt.body)
1074
+ if body_end is not None:
1075
+ self._finish(body_end, Fall(next_bb=cond_bb))
1076
+ finally:
1077
+ self._loop_stack.pop()
1078
+
1079
+ return exit_bb
1080
+
1081
+ # -- try/except/finally ---------------------------------------------
1082
+
1083
+ def _build_try(self, cur: int, stmt: TpyTry) -> int | None:
1084
+ finally_name: str | None = None
1085
+ finally_entry_bb: int | None = None
1086
+ captured_exc_field: str | None = None
1087
+ pending_return_flag: str | None = None
1088
+ pending_return_slot: str | None = None
1089
+ finally_async = bool(stmt.finally_body) and _stmts_have_any_suspension(
1090
+ stmt.finally_body)
1091
+ if stmt.finally_body and not finally_async:
1092
+ finally_name = f"__finally_{self._next_finally_id}"
1093
+ self._next_finally_id += 1
1094
+ self._finally_helpers.append((finally_name, list(stmt.finally_body)))
1095
+ elif finally_async:
1096
+ # CFG-based finally. Pre-scan assigns an exception-slot uid
1097
+ # and (if returns are reachable in any of try / handler /
1098
+ # finally) a pending-return slot. Nesting two CFG-based
1099
+ # finally regions is supported: the inner AsyncFinallyExit
1100
+ # forwards its pending state into the outer's parking slot
1101
+ # (see `_emit_async_finally_exit`).
1102
+ uid = self._try_finally_uid_map.get(id(stmt))
1103
+ if uid is None:
1104
+ raise _CFGNotYetSupported(
1105
+ "try-finally-with-await reached CFG builder "
1106
+ "without a registered exception-slot uid "
1107
+ "(internal: pre-scan missed it).",
1108
+ loc=stmt.loc,
1109
+ )
1110
+ captured_exc_field = f"__finally_exc_{uid}"
1111
+ # Pending-return slot: allocated when there's a reachable
1112
+ # `return` anywhere the CFG-based finally can intercept --
1113
+ # try body, any handler body, or the finally body itself
1114
+ # (the FinallyRegion mirrors the flag/slot so a return inside
1115
+ # the finally body parks into the same place).
1116
+ # `pending_return_slot` is None for void async defs (the
1117
+ # flag alone suffices).
1118
+ try_has_return = (_stmts_have_any_return(stmt.try_body)
1119
+ or any(_stmts_have_any_return(h.body)
1120
+ for h in stmt.handlers)
1121
+ or _stmts_have_any_return(stmt.finally_body))
1122
+ if try_has_return:
1123
+ pending_return_flag = f"__finally_pending_{uid}"
1124
+ # Void async defs need only the flag (no value to park);
1125
+ # leave `pending_return_slot` None so emit sites can
1126
+ # consult one source of truth (the slot name) instead
1127
+ # of also re-deriving void-ness.
1128
+ if self._func_returns_void:
1129
+ pending_return_slot = None
1130
+ else:
1131
+ pending_return_slot = f"__finally_ret_{uid}"
1132
+ else:
1133
+ pending_return_flag = None
1134
+ pending_return_slot = None
1135
+
1136
+ # Pre-allocate finally_entry_bb + finally_exit_bb (CFG-based
1137
+ # finally only) so TryRegion / FinallyRegion can carry them.
1138
+ # `finally_exit_bb` is the single replay site holding the
1139
+ # AsyncFinallyExit synth -- both normal fall-through from the
1140
+ # finally body and a return-in-finally jump here, which gives
1141
+ # the deferred-return replay a home outside the body's last BB.
1142
+ finally_region: FinallyRegion | None = None
1143
+ finally_exit_bb: int | None = None
1144
+ if finally_async:
1145
+ assert captured_exc_field is not None
1146
+ # finally_exit_bb is OUTSIDE the FinallyRegion -- by the time
1147
+ # AsyncFinallyExit runs, the finally body is done.
1148
+ finally_exit_bb = self._new_bb()
1149
+ finally_region = FinallyRegion(
1150
+ helper_name=captured_exc_field,
1151
+ loc_source=stmt,
1152
+ pending_return_flag=pending_return_flag,
1153
+ pending_return_slot=pending_return_slot,
1154
+ finally_exit_bb=finally_exit_bb,
1155
+ )
1156
+ self._region_stack.append(finally_region)
1157
+ finally_entry_bb = self._new_bb()
1158
+ self._region_stack.pop()
1159
+
1160
+ try_region = TryRegion(
1161
+ handlers=tuple(stmt.handlers),
1162
+ finally_helper_name=finally_name,
1163
+ tier=stmt.tier or "throw",
1164
+ loc_source=stmt,
1165
+ finally_entry_bb=finally_entry_bb,
1166
+ captured_exc_field=captured_exc_field,
1167
+ pending_return_flag=pending_return_flag,
1168
+ pending_return_slot=pending_return_slot,
1169
+ )
1170
+ # join_bb is outside the try frame.
1171
+ join_bb = self._new_bb()
1172
+
1173
+ # Target for normal try-body exit: finally_entry when CFG-based
1174
+ # finally (so finally body runs before reaching join); join
1175
+ # otherwise.
1176
+ normal_exit_target = (finally_entry_bb if finally_async
1177
+ else join_bb)
1178
+
1179
+ # Try body BBs live inside the TryRegion. Push the region BEFORE
1180
+ # creating them so their region_stack captures correctly.
1181
+ self._region_stack.append(try_region)
1182
+ try:
1183
+ try_body_bb = self._new_bb()
1184
+ self._finish(cur, Fall(next_bb=try_body_bb))
1185
+ try_end = self._build_block(try_body_bb, stmt.try_body)
1186
+ if try_end is not None:
1187
+ if stmt.else_body:
1188
+ else_end = self._build_block(try_end, stmt.else_body)
1189
+ if else_end is not None:
1190
+ self._finish(else_end,
1191
+ Fall(next_bb=normal_exit_target))
1192
+ else:
1193
+ self._finish(try_end,
1194
+ Fall(next_bb=normal_exit_target))
1195
+ finally:
1196
+ self._region_stack.pop()
1197
+
1198
+ # Except-handler bodies: the corresponding TryRegion is no
1199
+ # longer active inside the handler (the throw has been caught);
1200
+ # the ExceptRegion takes its place so emit knows we're in a
1201
+ # handler body (for sub-future reset, exception-binding scope).
1202
+ # When the parent try has a CFG-based finally, the
1203
+ # ExceptRegion also carries the parent's captured_exc /
1204
+ # finally_entry so emit can route raises and returns inside
1205
+ # the handler body through the finally region.
1206
+ for handler in stmt.handlers:
1207
+ except_region = ExceptRegion(
1208
+ handler=handler,
1209
+ parent_finally=try_region.finally_helper_name,
1210
+ parent_finally_entry_bb=try_region.finally_entry_bb,
1211
+ parent_pending_return_flag=try_region.pending_return_flag,
1212
+ parent_pending_return_slot=try_region.pending_return_slot,
1213
+ loc_source=stmt,
1214
+ )
1215
+ self._region_stack.append(except_region)
1216
+ try:
1217
+ handler_entry = self._new_bb()
1218
+ handler_end = self._build_block(handler_entry, handler.body)
1219
+ if handler_end is not None:
1220
+ # Handler's normal exit: when finally is CFG-based,
1221
+ # route through finally entry so the finally body
1222
+ # runs before reaching the post-try join.
1223
+ self._finish(handler_end,
1224
+ Fall(next_bb=normal_exit_target))
1225
+ self._record_handler_entry(try_region, handler, handler_entry)
1226
+ finally:
1227
+ self._region_stack.pop()
1228
+
1229
+ # Finally body (CFG-based path).
1230
+ if finally_async:
1231
+ assert finally_region is not None
1232
+ assert finally_entry_bb is not None
1233
+ assert finally_exit_bb is not None
1234
+ assert captured_exc_field is not None
1235
+ self._region_stack.append(finally_region)
1236
+ try:
1237
+ finally_end = self._build_block(
1238
+ finally_entry_bb, stmt.finally_body)
1239
+ if finally_end is not None:
1240
+ # Normal fall-through: route through finally_exit_bb
1241
+ # so AsyncFinallyExit (rethrow + deferred-return
1242
+ # replay) runs at the single replay site. A
1243
+ # return-in-finally inside the body also jumps to
1244
+ # finally_exit_bb (via the FinallyRegion's parking
1245
+ # info -- see gen_async._pending_return_info_for_region_stack).
1246
+ self._finish(finally_end, Fall(next_bb=finally_exit_bb))
1247
+ finally:
1248
+ self._region_stack.pop()
1249
+ # AsyncFinallyExit lives at the dedicated exit BB; fall to
1250
+ # join from there.
1251
+ self._blocks[finally_exit_bb].stmts.append(
1252
+ AsyncFinallyExit(
1253
+ captured_exc_field=captured_exc_field,
1254
+ pending_return_flag=pending_return_flag,
1255
+ pending_return_slot=pending_return_slot,
1256
+ ))
1257
+ self._finish(finally_exit_bb, Fall(next_bb=join_bb))
1258
+
1259
+ return join_bb
1260
+
1261
+ def _record_handler_entry(self, region: TryRegion,
1262
+ handler: TpyExceptHandler,
1263
+ entry_bb: int) -> None:
1264
+ # Stash on a per-builder side map keyed by (id(region), id(handler))
1265
+ # so the emit can find handler entries without mutating the
1266
+ # frozen region dataclass.
1267
+ self._handler_entries[(id(region), id(handler))] = entry_bb
1268
+
1269
+ def get_handler_entry(self, region: TryRegion,
1270
+ handler: TpyExceptHandler) -> int | None:
1271
+ return self._handler_entries.get((id(region), id(handler)))
1272
+
1273
+ # -- with -----------------------------------------------------------
1274
+
1275
+ def _build_with(self, cur: int, stmt: TpyWith) -> int | None:
1276
+ """Lower a sync `with X as t: body` whose body contains await
1277
+ into a CFG region. The context manager lives in a frame-hoisted
1278
+ field (`__with_ctx_<n>`); the case-emit wraps each case body in
1279
+ try/catch that runs `__exit__` on exception (with optional
1280
+ suppression -> transition to post-with state).
1281
+
1282
+ Async `with` (`stmt.is_async`) routes to a separate path: the
1283
+ `__aenter__` and `__aexit__` calls are themselves suspensions
1284
+ and need Yield BBs, modelled via a synthetic TryRegion whose
1285
+ CFG-based finally body holds the `__aexit__` yield. See
1286
+ `_build_async_with`."""
1287
+ if stmt.is_async:
1288
+ return self._build_async_with(cur, stmt)
1289
+ ctx_ns = self._with_uid_map.get(id(stmt))
1290
+ if ctx_ns is None:
1291
+ raise _CFGNotYetSupported(
1292
+ "with-stmt with await reached CFG builder without "
1293
+ "registered ctx uids (internal: pre-scan missed it).",
1294
+ loc=stmt.loc,
1295
+ )
1296
+ if len(ctx_ns) != len(stmt.items):
1297
+ raise _CFGNotYetSupported(
1298
+ "with-stmt ctx uid count mismatch (internal).",
1299
+ loc=stmt.loc,
1300
+ )
1301
+ post_with_bb = self._new_bb()
1302
+ # For each item, push WithRegion then emit WithEnter into
1303
+ # the current BB. Items are processed in source order
1304
+ # (outer-most CM enters first).
1305
+ regions: list[WithRegion] = []
1306
+ for item, ctx_n in zip(stmt.items, ctx_ns):
1307
+ region = WithRegion(
1308
+ item=item,
1309
+ ctx_n=ctx_n,
1310
+ post_with_bb=post_with_bb,
1311
+ loc_source=stmt,
1312
+ )
1313
+ self._blocks[cur].stmts.append(
1314
+ WithEnter(ctx_n=ctx_n, item=item))
1315
+ self._region_stack.append(region)
1316
+ regions.append(region)
1317
+ try:
1318
+ # Body BBs live inside all WithRegions.
1319
+ body_entry = self._new_bb()
1320
+ self._finish(cur, Fall(next_bb=body_entry))
1321
+ body_end = self._build_block(body_entry, stmt.body)
1322
+ if body_end is not None:
1323
+ # Fall out -> exit BB. Region exit (normal __exit__) is
1324
+ # emitted via `_emit_exit_region_finallies` when the
1325
+ # transition crosses out of the With regions.
1326
+ self._finish(body_end, Fall(next_bb=post_with_bb))
1327
+ finally:
1328
+ # Pop regions in reverse so the region_stack is clean
1329
+ # outside this with.
1330
+ for _ in regions:
1331
+ self._region_stack.pop()
1332
+ return post_with_bb
1333
+
1334
+ # -- async with -----------------------------------------------------
1335
+
1336
+ def _build_async_with(self, cur: int, stmt: TpyWith) -> int | None:
1337
+ """Lower Python `async with X1 as a, X2 as b: body` (v1.5 M5).
1338
+
1339
+ Each item is decomposed left-to-right into:
1340
+ AsyncWithSetup -> Yield(__aenter__) -> [TryRegion {
1341
+ <recurse to next item, or body>
1342
+ } finally {
1343
+ Yield(__aexit__) ; AsyncFinallyExit
1344
+ }]
1345
+
1346
+ The TryRegion's finally body re-uses the M3.3 CFG-based-finally
1347
+ machinery: a `__finally_exc_<uid>` frame slot saves any in-flight
1348
+ exception via `std::current_exception()`; AsyncFinallyExit at
1349
+ the tail rethrows. `return` inside the body parks the value in
1350
+ `__finally_pending_<uid>` / `__finally_ret_<uid>` and walks
1351
+ through the same finally path (same as M3.3.2).
1352
+
1353
+ v1.5 M5 cleanup-only: `__aexit__` is called with all-None args
1354
+ regardless of whether an exception was caught. Inspecting
1355
+ `exc_val: Optional[BaseException]` is deferred to the
1356
+ polymorphic-exception-storage milestone (E9).
1357
+ """
1358
+ ctx_ns = self._with_uid_map.get(id(stmt))
1359
+ if ctx_ns is None:
1360
+ raise _CFGNotYetSupported(
1361
+ "async-with reached CFG builder without registered "
1362
+ "ctx uids (internal: pre-scan missed it).",
1363
+ loc=stmt.loc,
1364
+ )
1365
+ if len(ctx_ns) != len(stmt.items):
1366
+ raise _CFGNotYetSupported(
1367
+ "async-with ctx uid count mismatch (internal).",
1368
+ loc=stmt.loc,
1369
+ )
1370
+ finally_uid = self._try_finally_uid_map.get(id(stmt))
1371
+ if finally_uid is None:
1372
+ raise _CFGNotYetSupported(
1373
+ "async-with reached CFG builder without registered "
1374
+ "finally-exc uid (internal: pre-scan missed it).",
1375
+ loc=stmt.loc,
1376
+ )
1377
+ # Multi-item `async with X as a, Y as b:`: would left-to-right
1378
+ # desugar to nested `async with` using the same finally_uid, so
1379
+ # the items would collide on the shared `__finally_exc_<n>` /
1380
+ # `__finally_pending_<n>` / `__finally_ret_<n>` fields. Reject
1381
+ # for now; users can nest two `async with` statements instead.
1382
+ if len(stmt.items) != 1:
1383
+ raise _CFGNotYetSupported(
1384
+ "multi-item `async with X as a, Y as b:` is not yet "
1385
+ "supported -- nest two `async with` statements instead",
1386
+ loc=stmt.loc,
1387
+ )
1388
+ # An `async with` synthesizes its own TryRegion with a CFG-based
1389
+ # finally body (the `__aexit__` Yield). Plain try/finally
1390
+ # nesting was lifted by extending AsyncFinallyExit to forward
1391
+ # parked state outward; the async-with case has not been lifted
1392
+ # because the synthesized finally also needs to keep its
1393
+ # __aexit__ call ordering correct across forwarding.
1394
+ for r in self._region_stack:
1395
+ if (isinstance(r, TryRegion)
1396
+ and r.captured_exc_field is not None):
1397
+ raise _CFGNotYetSupported(
1398
+ "`async with` nested inside another `await`-in-"
1399
+ "finally region is a planned follow-up.",
1400
+ loc=stmt.loc,
1401
+ )
1402
+
1403
+ item = stmt.items[0]
1404
+ ctx_n = ctx_ns[0]
1405
+ captured_exc_field = f"__finally_exc_{finally_uid}"
1406
+
1407
+ # 1. AsyncWithSetup populates the __with_ctx_<n> frame slot.
1408
+ self._blocks[cur].stmts.append(
1409
+ AsyncWithSetup(ctx_n=ctx_n, item=item))
1410
+
1411
+ # 2. Yield for `__aenter__()`. Synthesize a minimal TpyAwait so
1412
+ # the payload's dataclass invariants hold; emit branches on
1413
+ # `async_with_kind` and never reads the AST. The sub-coro
1414
+ # struct's C++ name is resolved at codegen time by looking up
1415
+ # `_async_with_struct_names[ctx_n]` (populated by the prescan).
1416
+ dummy_await = TpyAwait(value=item.context_expr, loc=stmt.loc)
1417
+ aenter_resume_bb = self._new_bb()
1418
+ aenter_payload = AwaitPayload(
1419
+ mode=AwaitMode.INLINE,
1420
+ sub_field_cpp_type="", # emit fills this from the CM type
1421
+ operand_expr=item.context_expr,
1422
+ kind=(AwaitKind.ASSIGN if item.target is not None
1423
+ else AwaitKind.DISCARD),
1424
+ bind_target=item.target,
1425
+ return_stmt=None,
1426
+ host_stmt=stmt,
1427
+ await_node=dummy_await,
1428
+ async_with_kind=AsyncWithKind.AENTER,
1429
+ async_with_ctx_n=ctx_n,
1430
+ )
1431
+ aenter_yield = Yield(
1432
+ payload=aenter_payload,
1433
+ resume_bb=aenter_resume_bb,
1434
+ suspension_index=len(self._yield_sites),
1435
+ )
1436
+ self._finish(cur, aenter_yield)
1437
+ self._yield_sites.append(aenter_yield)
1438
+
1439
+ # 3. Build TryRegion with CFG-based finally. The body is
1440
+ # stmt.body; the finally body holds the __aexit__ Yield +
1441
+ # AsyncFinallyExit.
1442
+ pending_return_flag = None
1443
+ pending_return_slot = None
1444
+ if _stmts_have_any_return(stmt.body):
1445
+ pending_return_flag = f"__finally_pending_{finally_uid}"
1446
+ if not self._func_returns_void:
1447
+ pending_return_slot = f"__finally_ret_{finally_uid}"
1448
+
1449
+ # Build finally body BB (with FinallyRegion on stack).
1450
+ finally_region = FinallyRegion(
1451
+ helper_name=captured_exc_field,
1452
+ loc_source=stmt,
1453
+ )
1454
+ self._region_stack.append(finally_region)
1455
+ finally_entry_bb = self._new_bb()
1456
+ self._region_stack.pop()
1457
+
1458
+ try_region = TryRegion(
1459
+ handlers=(),
1460
+ finally_helper_name=None,
1461
+ tier="throw",
1462
+ loc_source=stmt,
1463
+ finally_entry_bb=finally_entry_bb,
1464
+ captured_exc_field=captured_exc_field,
1465
+ pending_return_flag=pending_return_flag,
1466
+ pending_return_slot=pending_return_slot,
1467
+ )
1468
+ join_bb = self._new_bb()
1469
+
1470
+ # Try body lives inside TryRegion.
1471
+ self._region_stack.append(try_region)
1472
+ try:
1473
+ try_body_entry = self._new_bb()
1474
+ self._finish(aenter_resume_bb, Fall(next_bb=try_body_entry))
1475
+ body_end = self._build_block(try_body_entry, stmt.body)
1476
+ if body_end is not None:
1477
+ self._finish(body_end, Fall(next_bb=finally_entry_bb))
1478
+ finally:
1479
+ self._region_stack.pop()
1480
+
1481
+ # Finally body: Yield for __aexit__ + AsyncFinallyExit + Fall
1482
+ # to join.
1483
+ self._region_stack.append(finally_region)
1484
+ try:
1485
+ dummy_aexit_await = TpyAwait(
1486
+ value=item.context_expr, loc=stmt.loc)
1487
+ aexit_resume_bb = self._new_bb()
1488
+ aexit_payload = AwaitPayload(
1489
+ mode=AwaitMode.INLINE,
1490
+ sub_field_cpp_type="",
1491
+ operand_expr=item.context_expr,
1492
+ kind=AwaitKind.DISCARD,
1493
+ bind_target=None,
1494
+ return_stmt=None,
1495
+ host_stmt=stmt,
1496
+ await_node=dummy_aexit_await,
1497
+ async_with_kind=AsyncWithKind.AEXIT,
1498
+ async_with_ctx_n=ctx_n,
1499
+ )
1500
+ aexit_yield = Yield(
1501
+ payload=aexit_payload,
1502
+ resume_bb=aexit_resume_bb,
1503
+ suspension_index=len(self._yield_sites),
1504
+ )
1505
+ self._finish(finally_entry_bb, aexit_yield)
1506
+ self._yield_sites.append(aexit_yield)
1507
+ # After aexit resume: AsyncFinallyExit (rethrow saved exc if
1508
+ # any, deferred return if pending), then Fall to join.
1509
+ self._blocks[aexit_resume_bb].stmts.append(
1510
+ AsyncFinallyExit(
1511
+ captured_exc_field=captured_exc_field,
1512
+ pending_return_flag=pending_return_flag,
1513
+ pending_return_slot=pending_return_slot,
1514
+ ))
1515
+ self._finish(aexit_resume_bb, Fall(next_bb=join_bb))
1516
+ finally:
1517
+ self._region_stack.pop()
1518
+
1519
+ return join_bb
1520
+
1521
+ # -- match -----------------------------------------------------------
1522
+
1523
+ def _build_match(self, cur: int, stmt: TpyMatch) -> int | None:
1524
+ """Lower a `match` whose arm bodies contain a suspension. The
1525
+ dispatch stays a single suspension-free unit (emitted later by
1526
+ reusing `gen_match`); each arm body becomes its own BB chain
1527
+ (recursively built, so nested suspensions decompose), joining at
1528
+ `join_bb`. `match` introduces no region, so arm BBs and join
1529
+ share `cur`'s region stack -- no finally-chain delta on the
1530
+ dispatch->arm or arm->join transitions.
1531
+
1532
+ A guard or the subject expression cannot host a suspension (those
1533
+ appear only at statement position), so the dispatch is always
1534
+ suspension-free; only arm bodies suspend."""
1535
+ join_bb = self._new_bb()
1536
+ arm_bbs: list[int] = []
1537
+ all_terminate = True
1538
+ for case in stmt.cases:
1539
+ arm_bb = self._new_bb()
1540
+ arm_bbs.append(arm_bb)
1541
+ arm_end = self._build_block(arm_bb, case.body)
1542
+ if arm_end is not None:
1543
+ self._finish(arm_end, Fall(next_bb=join_bb))
1544
+ all_terminate = False
1545
+ # When the match is exhaustive AND every arm terminates, no
1546
+ # control path reaches past the match: the dispatch has no
1547
+ # fall-through and join is unreachable.
1548
+ reachable_join = None if (stmt.is_exhaustive and all_terminate) else join_bb
1549
+ self._finish(cur, MatchDispatch(
1550
+ match_stmt=stmt,
1551
+ arm_bbs=tuple(arm_bbs),
1552
+ join_bb=reachable_join,
1553
+ ))
1554
+ return reachable_join
1555
+
1556
+ # -- post-construction passes ---------------------------------------
1557
+
1558
+ def _mark_resume_entries(self, cfg: CFG) -> None:
1559
+ for y in cfg.yield_sites:
1560
+ cfg.blocks[y.resume_bb].is_resume_entry = True
1561
+
1562
+
1563
+ # -- Errors ---------------------------------------------------------------
1564
+
1565
+ class _CFGNotYetSupported(Exception):
1566
+ """Raised when the CFG builder hits a shape that's known but not yet
1567
+ implemented. Caller catches and converts to a CodeGenError with the
1568
+ appropriate location.
1569
+ """
1570
+ def __init__(self, msg: str, loc) -> None:
1571
+ super().__init__(msg)
1572
+ self.msg = msg
1573
+ self.loc = loc
1574
+
1575
+
1576
+ # -- Helpers (top-level, used by gen_async too) ---------------------------
1577
+
1578
+ def _top_level_await_in(stmt: TpyStmt) -> TpyAwait | None:
1579
+ """Return the TpyAwait if `stmt` is one of the top-level
1580
+ statement-position await shapes: assign with await RHS, vardecl
1581
+ with await init, return with await value, or expression-statement
1582
+ with bare await. Returns None otherwise.
1583
+ """
1584
+ value: TpyExpr | None
1585
+ if isinstance(stmt, TpyAssign):
1586
+ value = stmt.value
1587
+ elif isinstance(stmt, TpyVarDecl):
1588
+ value = stmt.init
1589
+ elif isinstance(stmt, TpyReturn):
1590
+ value = stmt.value
1591
+ elif isinstance(stmt, TpyExprStmt):
1592
+ value = stmt.expr
1593
+ else:
1594
+ value = None
1595
+ if isinstance(value, TpyAwait):
1596
+ return value
1597
+ return None
1598
+
1599
+
1600
+ def _classify_await_position(
1601
+ stmt: TpyStmt, await_node: TpyAwait,
1602
+ ) -> tuple[AwaitKind, str | None, TpyReturn | None]:
1603
+ """Return (kind, bind_target, return_stmt) for a top-level await."""
1604
+ if isinstance(stmt, TpyAssign) and stmt.value is await_node:
1605
+ target = stmt.target
1606
+ if not isinstance(target, TpyName):
1607
+ raise _CFGNotYetSupported(
1608
+ "await result can only be bound to a simple name in v1; "
1609
+ "field/index targets are not yet supported.",
1610
+ loc=stmt.loc,
1611
+ )
1612
+ return (AwaitKind.ASSIGN, target.name, None)
1613
+ if isinstance(stmt, TpyVarDecl) and stmt.init is await_node:
1614
+ return (AwaitKind.VARDECL, stmt.name, None)
1615
+ if isinstance(stmt, TpyReturn) and stmt.value is await_node:
1616
+ return (AwaitKind.RETURN, None, stmt)
1617
+ if isinstance(stmt, TpyExprStmt) and stmt.expr is await_node:
1618
+ return (AwaitKind.DISCARD, None, None)
1619
+ raise _CFGNotYetSupported(
1620
+ "internal: _classify_await_position called on unsupported stmt.",
1621
+ loc=stmt.loc,
1622
+ )
1623
+
1624
+
1625
+
1626
+
1627
+
1628
+
1629
+ def _stmt_has_unbound_loop_transfer(stmt: TpyStmt) -> bool:
1630
+ """True if `stmt` directly or transitively contains a break/continue
1631
+ that targets the enclosing loop (not bound by an inner loop in this
1632
+ statement). Used to decide whether a leaf compound (if/try/with)
1633
+ inside a CFG-decomposed loop needs decomposition so its break /
1634
+ continue translate to state transitions.
1635
+
1636
+ Stops at nested loops (while/for): their own break/continue bind
1637
+ to them and stay as C++ break/continue (the nested loop is a
1638
+ leaf compound)."""
1639
+ if isinstance(stmt, (TpyBreak, TpyContinue)):
1640
+ return True
1641
+ # Nested loop swallows its own break/continue.
1642
+ if isinstance(stmt, (TpyWhile, TpyForEach)):
1643
+ return False
1644
+ if hasattr(stmt, "sub_bodies"):
1645
+ for body in stmt.sub_bodies():
1646
+ for s in body:
1647
+ if _stmt_has_unbound_loop_transfer(s):
1648
+ return True
1649
+ return False
1650
+
1651
+