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,2064 @@
1
+ """
2
+ TurboPython Code Generation Context
3
+
4
+ Shared state and utilities for C++ code generation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ import re
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum, auto
11
+ from typing import Callable, Iterator, Literal, TextIO, TYPE_CHECKING
12
+
13
+ from ..typesys import (
14
+ TpyType, PtrType, OwnType, ReadonlyType, OptionalType, NominalType, SelfType,
15
+ IntLiteralType, TypeParamRef, UnionType, TupleType, FunctionInfo, ModuleInfo,
16
+ AliasRef, RecursiveUnionInfo,
17
+ is_protocol_type, unwrap_readonly, unwrap_qualifiers, ensure_qualified, unwrap_ref_type,
18
+ is_union_or_optional_type, is_own_pointer_repr_optional,
19
+ polymorphic_source_is_pointer,
20
+ )
21
+ from ..parse import (
22
+ SourceLocation, TpyExpr, TpyIntLiteral, TpyFloatLiteral, TpyStrLiteral,
23
+ TpyBoolLiteral, TpyNoneLiteral, TpyArrayLiteral, TpyListRepeat, TpyListComprehension,
24
+ TpyDictLiteral, TpySetLiteral, TpyDictComprehension, TpySetComprehension,
25
+ TpyGeneratorExpression,
26
+ TpyCoerce, TpyBinOp, TpyUnaryOp, TpyMethodCall, TpySubscript, TpySlice, TpyCall, TpyName, TpyFieldAccess,
27
+ TpyIfExpr,
28
+ )
29
+ from ..namespace import Namespace, BindingKind
30
+ from ..type_def_registry import (
31
+ is_bool_type, is_dict, is_set, is_bytes_view_type, is_str_view_type,
32
+ )
33
+ from ..symbol_binding import lookup_imported, SymbolKind
34
+ from ..compilation_context import get_current_compiler
35
+
36
+ if TYPE_CHECKING:
37
+ from ..sema import SemanticAnalyzer
38
+
39
+
40
+ INDENT = " "
41
+
42
+ # C++ reserved keywords and common type names that conflict with Python identifiers.
43
+ # When a Python name matches one of these, codegen appends '_' to avoid C++ errors.
44
+ _CPP_RESERVED_WORDS: frozenset[str] = frozenset({
45
+ # C++ keywords
46
+ "alignas", "alignof", "and", "and_eq", "asm", "auto", "bitand", "bitor",
47
+ "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t",
48
+ "class", "compl", "concept", "const", "consteval", "constexpr", "constinit",
49
+ "const_cast", "continue", "co_await", "co_return", "co_yield",
50
+ "decltype", "default", "delete", "do", "double", "dynamic_cast",
51
+ "else", "enum", "explicit", "export", "extern",
52
+ "false", "float", "for", "friend", "goto",
53
+ "if", "inline", "int", "long", "mutable",
54
+ "namespace", "new", "noexcept", "not", "not_eq", "nullptr",
55
+ "operator", "or", "or_eq",
56
+ "private", "protected", "public",
57
+ "register", "reinterpret_cast", "requires", "return",
58
+ "short", "signed", "sizeof", "static", "static_assert", "static_cast",
59
+ "struct", "switch",
60
+ "template", "this", "thread_local", "throw", "true", "try", "typedef",
61
+ "typeid", "typename",
62
+ "union", "unsigned", "using",
63
+ "virtual", "void", "volatile",
64
+ "wchar_t", "while",
65
+ "xor", "xor_eq",
66
+ })
67
+
68
+
69
+ def escape_cpp_name(name: str) -> str:
70
+ """Escape a Python identifier that clashes with a C++ reserved word.
71
+
72
+ Appends '_' to names that collide with C++ keywords or built-in type names.
73
+ Leaves other names unchanged. Dunder names (__x__) and internal names
74
+ (starting with __tpy) are never escaped -- they are compiler-generated.
75
+ """
76
+ if name in _CPP_RESERVED_WORDS:
77
+ return name + "_"
78
+ return name
79
+
80
+
81
+ _TEMPLATE_PLACEHOLDER = re.compile(r"\{(self|cpp|\d+)\}")
82
+
83
+
84
+ def expand_cpp_template(template: str, self_val: str, *args: str,
85
+ self_type: 'TpyType | None' = None) -> str:
86
+ """Expand a C++ template, substituting {self}, {cpp}, and positional {0}, {1}, etc.
87
+
88
+ {cpp} is replaced with self_type.to_cpp() when available -- used for
89
+ @builtin_type methods that reference their own C++ type name.
90
+ """
91
+ # Validate before substitution: check that all placeholder indices are in range.
92
+ # Post-substitution scanning would false-positive on C++ braces in values
93
+ # (e.g. std::vector<int>{30} looks like {30} placeholder).
94
+ for m in _TEMPLATE_PLACEHOLDER.finditer(template):
95
+ token = m.group(1)
96
+ if token in ("self", "cpp"):
97
+ continue
98
+ if int(token) >= len(args):
99
+ raise CodeGenError(
100
+ f"Unreplaced placeholder {m.group()} in C++ template: {template}"
101
+ )
102
+ if self_type and "{cpp}" in template:
103
+ template = template.replace("{cpp}", self_type.to_cpp())
104
+ result = template.replace("{self}", self_val)
105
+ for i, arg in enumerate(args):
106
+ result = result.replace(f"{{{i}}}", arg)
107
+ return result
108
+
109
+
110
+ def escape_cpp_string(value: str) -> str:
111
+ """Escape a Python string for use in a C++ string literal (double-quoted)."""
112
+ return (value.replace('\\', '\\\\')
113
+ .replace('"', '\\"')
114
+ .replace('\n', '\\n')
115
+ .replace('\r', '\\r')
116
+ .replace('\t', '\\t')
117
+ .replace('\x00', '\\000'))
118
+
119
+
120
+ def cpp_bytes_literal_span(value: bytes) -> str:
121
+ """Render a bytes literal as a `::tpy::bytes_literal(...)` call --
122
+ a `std::span<const uint8_t>` over a C++ string literal (static
123
+ storage), avoiding the heap allocation of a temporary vector."""
124
+ escaped = "".join(f"\\x{b:02x}" for b in value)
125
+ return f'::tpy::bytes_literal("{escaped}", {len(value)})'
126
+
127
+
128
+ def cpp_string_literal_expr(value: str) -> str:
129
+ """Render a Python string as a C++ string_view expression.
130
+
131
+ NUL-free literals emit as bare `"..."` (decay; string_view ctor uses
132
+ strlen). Embedded NUL needs `std::string_view{"...", N}` with explicit
133
+ UTF-8 byte length, otherwise strlen truncates at the first NUL.
134
+ """
135
+ escaped = escape_cpp_string(value)
136
+ if '\x00' in value:
137
+ nbytes = len(value.encode('utf-8'))
138
+ return f'std::string_view{{"{escaped}", {nbytes}}}'
139
+ return f'"{escaped}"'
140
+
141
+
142
+ def view_key_target(container_type) -> "TpyType | None":
143
+ """For dict/set with a view-typed key (BytesView/StrView), return that
144
+ key type so callers can thread it as a target_type into key-position
145
+ codegen. Lets bytes/str literals pin to static storage instead of
146
+ being stored as dangling spans/string_views."""
147
+ if not (is_dict(container_type) or is_set(container_type)):
148
+ return None
149
+ type_args = getattr(container_type, "type_args", None)
150
+ if not type_args:
151
+ return None
152
+ k = type_args[0]
153
+ if is_bytes_view_type(k) or is_str_view_type(k):
154
+ return k
155
+ return None
156
+
157
+
158
+ def escape_cpp_char(value: str) -> str:
159
+ """Escape a Python char for use in a C++ char literal (single-quoted)."""
160
+ return (value.replace('\\', '\\\\').replace("'", "\\'")
161
+ .replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
162
+ .replace('\x00', '\\000'))
163
+
164
+
165
+ def get_include_path(module_name: str) -> str | None:
166
+ """Get the include path override for a module, or None for default.
167
+
168
+ Returns None when no compilation is active (matches the previous
169
+ empty-default-dict behavior for callers like BuildLayout's pure
170
+ path-shape tests).
171
+ """
172
+ compiler = get_current_compiler()
173
+ if compiler is None:
174
+ return None
175
+ return compiler.include_path_map.get(module_name)
176
+
177
+
178
+ def module_to_include_path(module_name: str) -> str:
179
+ """Resolve include path for a module: override if set, else default from name."""
180
+ override = get_include_path(module_name)
181
+ if override is not None:
182
+ return override
183
+ parts = module_name.split('.')
184
+ if len(parts) == 1:
185
+ return f"{parts[0]}.hpp"
186
+ return '/'.join(parts[:-1]) + f"/{parts[-1]}.hpp"
187
+
188
+
189
+ # Mapping from Python dunder methods to C++ binary operators.
190
+ # Both __truediv__ and __floordiv__ map to / in C++: for integer types, C++ /
191
+ # is truncating division (like Python //); user types should implement the
192
+ # appropriate semantics in their __truediv__/__floordiv__ methods.
193
+ def module_to_cpp_namespace(module_name: str) -> str:
194
+ """Convert a dotted module name to a C++ namespace.
195
+
196
+ Checks the namespace map first (for # tpy: namespace overrides),
197
+ falls back to "tpyapp::{module_name}".
198
+ """
199
+ compiler = get_current_compiler()
200
+ if compiler is not None and module_name in compiler.namespace_map:
201
+ return compiler.namespace_map[module_name]
202
+ return f"tpyapp::{module_name.replace('.', '::')}"
203
+
204
+
205
+ def module_has_cpp_namespace_override(module_name: str) -> bool:
206
+ """True if `module_name` has an explicit `# tpy: cpp_namespace` directive
207
+ (i.e. its C++ namespace differs from the default `tpyapp::<module>`)."""
208
+ compiler = get_current_compiler()
209
+ return compiler is not None and module_name in compiler.namespace_map
210
+
211
+
212
+ def qualified_cpp_name(module_name: str, name: str) -> str:
213
+ """Build an absolute-qualified C++ name for cross-module references.
214
+
215
+ Example: ("shapes", "Circle") -> "::tpyapp::shapes::Circle"
216
+ Example: ("shapes", "Container.Inner") -> "::tpyapp::shapes::Container::Inner"
217
+ """
218
+ cpp_name = "::".join(escape_cpp_name(part) for part in name.split("."))
219
+ return f"::{module_to_cpp_namespace(module_name)}::{cpp_name}"
220
+
221
+
222
+ def qualify_native_name(name: str) -> str:
223
+ """Force @native call-site emission to absolute global scope.
224
+
225
+ Any non-empty name gets a leading `::` so C++ unqualified lookup can't
226
+ bind it to a member function, enclosing-namespace symbol, or ADL hit
227
+ before finding the intended native symbol. Idempotent: names that
228
+ already start with `::` are left alone.
229
+
230
+ Example: "socket" -> "::socket", "tpy::__len__" -> "::tpy::__len__",
231
+ "::already_global" -> "::already_global".
232
+ """
233
+ if not name or name.startswith("::"):
234
+ return name
235
+ return f"::{name}"
236
+
237
+
238
+ def enum_cpp_name(enum_type: TpyType, current_module: str, *,
239
+ absolute: bool = False, einfo=None) -> str:
240
+ """Authoritative C++ spelling for an enum type.
241
+
242
+ Resolution order:
243
+ - @native qname (canonical, sema-normalized to ::-prefixed form);
244
+ - cross-module imported qualification (driven by EnumInfo.module_name);
245
+ - local / nested form.
246
+
247
+ `absolute=False` (default): bare short / nested form for local
248
+ enums, which works inside the declaring module's user namespace.
249
+ `absolute=True`: fully-qualify local enums as
250
+ `::tpyapp::<module>::E` for sites that emit at global scope
251
+ (EnumUtil specializations, std::ostream operators outside the
252
+ user namespace block).
253
+
254
+ `einfo` lets callers hoist a previously-resolved EnumInfo so this
255
+ helper doesn't repeat the lookup (hot in member-access codegen).
256
+ """
257
+ if einfo is None:
258
+ from ..type_def_registry import enum_info_of
259
+ einfo = enum_info_of(enum_type)
260
+ if einfo is not None and einfo.is_native and einfo.native_name:
261
+ return einfo.native_name
262
+ if (einfo is not None and einfo.module_name is not None
263
+ and einfo.module_name != current_module):
264
+ return qualified_cpp_name(einfo.module_name, enum_type.name)
265
+ if absolute:
266
+ return qualified_cpp_name(current_module, enum_type.name)
267
+ if "." in enum_type.name:
268
+ return enum_type.name.replace(".", "::")
269
+ return enum_type.name
270
+
271
+
272
+ def loop_var_binding(
273
+ elem_type: TpyType, cpp_var: str, deref_expr: str,
274
+ const_loop_var: bool, hoisted: bool = False,
275
+ consuming: bool = False,
276
+ ) -> str:
277
+ """Return the C++ loop variable binding line (no trailing newline).
278
+
279
+ Shared by for-loop and comprehension codegen to avoid duplicating the
280
+ const_loop_var / value_type / auto&& decision tree.
281
+
282
+ consuming=True uses auto&& to bind into OwnIter's move-iterator
283
+ storage. Zero cost (no per-element move), but move-ready: codegen
284
+ can later emit std::move(var) for per-element ownership transfer.
285
+ """
286
+ # Own[T] from consuming iterators uses the same binding as T.
287
+ if isinstance(elem_type, OwnType):
288
+ elem_type = elem_type.wrapped
289
+ if hoisted:
290
+ return f"{cpp_var} = {deref_expr};"
291
+ if consuming:
292
+ # Forwarding ref into OwnIter storage: zero-cost, move-ready.
293
+ return f"auto&& {cpp_var} = {deref_expr};"
294
+ # Composite types (variants, tuples) use reference binding -- they may
295
+ # contain heap-allocated members, making copies expensive.
296
+ if isinstance(elem_type, (UnionType, TupleType)):
297
+ if const_loop_var:
298
+ return f"const auto& {cpp_var} = {deref_expr};"
299
+ return f"auto&& {cpp_var} = {deref_expr};"
300
+ if const_loop_var and elem_type.is_value_type():
301
+ if elem_type.is_expensive_copy():
302
+ return f"const {elem_type.to_cpp()}& {cpp_var} = {deref_expr};"
303
+ return f"{elem_type.to_cpp()} {cpp_var} = {deref_expr};"
304
+ if const_loop_var:
305
+ return f"const auto& {cpp_var} = {deref_expr};"
306
+ if elem_type.is_value_type():
307
+ return f"{elem_type.to_cpp()} {cpp_var} = {deref_expr};"
308
+ return f"auto&& {cpp_var} = {deref_expr};"
309
+
310
+
311
+ def is_lvalue_iterable(
312
+ expr: TpyExpr,
313
+ get_record: Callable[[str], object | None],
314
+ get_type: Callable[[TpyExpr], TpyType],
315
+ ) -> bool:
316
+ """Check if an iterable expression is a C++ lvalue.
317
+
318
+ Lvalue expressions get ``auto&`` to preserve consumption semantics.
319
+ Rvalue expressions (constructors, value-returning calls, literals)
320
+ get ``auto`` to own the temporary safely.
321
+
322
+ get_record: look up a record by name (e.g. registry.get_record).
323
+ get_type: resolve the C++ result type of an expression.
324
+ """
325
+ while isinstance(expr, TpyCoerce):
326
+ expr = expr.expr
327
+ if isinstance(expr, TpyName):
328
+ return True
329
+ if isinstance(expr, TpyFieldAccess):
330
+ return is_lvalue_iterable(expr.obj, get_record, get_type)
331
+ if isinstance(expr, TpySubscript):
332
+ # Slicing (a[1:]) returns an rvalue span/view, not a reference into obj.
333
+ # Only single-index subscripting (a[0]) returns T& and inherits lvalueness from obj.
334
+ if isinstance(expr.index, TpySlice):
335
+ return False
336
+ return is_lvalue_iterable(expr.obj, get_record, get_type)
337
+ if isinstance(expr, (TpyMethodCall, TpyCall)):
338
+ if isinstance(expr, TpyCall) and expr.call_type is not None:
339
+ return False
340
+ if isinstance(expr, TpyCall) and isinstance(expr.func, TpyName) and get_record(expr.func_name):
341
+ return False
342
+ # Own[T] returns are by-value rvalues even when T is a reference type;
343
+ # get_type strips OwnType, so consult resolved_function_info to see it.
344
+ rfi = expr.resolved_function_info
345
+ if rfi is not None and isinstance(rfi.return_type, OwnType):
346
+ return False
347
+ ret_type = get_type(expr)
348
+ # Protocol return types (e.g. Iterator[T] from generators) are
349
+ # value types in practice -- the C++ return is a concrete struct.
350
+ if is_protocol_type(ret_type):
351
+ return False
352
+ return (not ret_type.is_value_type()
353
+ and not is_union_or_optional_type(ret_type))
354
+ return False
355
+
356
+
357
+ DUNDER_TO_BINARY_OP: dict[str, str] = {
358
+ "__add__": "+", "__sub__": "-", "__mul__": "*",
359
+ "__truediv__": "/", "__floordiv__": "/", "__mod__": "%",
360
+ "__eq__": "==", "__ne__": "!=",
361
+ "__lt__": "<", "__le__": "<=", "__gt__": ">", "__ge__": ">=",
362
+ "__and__": "&", "__or__": "|", "__xor__": "^",
363
+ "__lshift__": "<<", "__rshift__": ">>",
364
+ }
365
+
366
+ # Container/generator-shaped expressions whose gen_expr emits a value
367
+ # (`vector<T>{...}`, `ordered_map<K, V>{...}`, generator state struct, ...)
368
+ # regardless of the target type. Distinct from pointer-emit rvalues
369
+ # (function calls returning T*, pointer-local names) which already yield
370
+ # stable pointer storage. Codegen sites initializing a pointer-form slot
371
+ # from an Optional source use this to decide whether to materialize a
372
+ # named slot before taking address.
373
+ _CONTAINER_LITERAL_NODES: tuple = (
374
+ TpyArrayLiteral, TpyListRepeat, TpyListComprehension,
375
+ TpyDictLiteral, TpySetLiteral,
376
+ TpyDictComprehension, TpySetComprehension,
377
+ TpyGeneratorExpression,
378
+ )
379
+
380
+
381
+ class SlotState:
382
+ """Manages unique slot names for pointer-local backing storage."""
383
+
384
+ def __init__(self):
385
+ self._counter: int = 0
386
+ self._prefix: str = "__slot"
387
+ self._global_scope: bool = False
388
+
389
+ @property
390
+ def global_scope(self) -> bool:
391
+ return self._global_scope
392
+
393
+ def next_slot(self) -> str:
394
+ """Return a fresh slot name (__slot_N or __global_slot_N)."""
395
+ self._counter += 1
396
+ return f"{self._prefix}_{self._counter}"
397
+
398
+ def reset(self, *, global_scope: bool = False) -> None:
399
+ self._counter = 0
400
+ self._global_scope = global_scope
401
+ self._prefix = "__global_slot" if global_scope else "__slot"
402
+
403
+
404
+ class TempState:
405
+ """Manages temporary variables for array literals passed to mutable reference params."""
406
+
407
+ def __init__(self):
408
+ self._pending: list[tuple[str, str, str, bool]] = []
409
+ self._pending_named: list[tuple[str, str, str | None, bool]] = []
410
+ self._counter: int = 0
411
+
412
+ def create(self, param_type: TpyType, init_expr: str) -> str:
413
+ """Create a temp variable and return its name for use in the call."""
414
+ param_type = unwrap_ref_type(param_type)
415
+ self._counter += 1
416
+ temp_name = f"__tmp_{self._counter}"
417
+ is_protocol = is_protocol_type(param_type)
418
+ type_cpp = "auto" if is_protocol or isinstance(param_type, TypeParamRef) else param_type.to_cpp()
419
+ self._pending.append((temp_name, type_cpp, init_expr, False))
420
+ return temp_name
421
+
422
+ def create_typed(self, cpp_type: str, init_expr: str, *, brace_init: bool = False) -> str:
423
+ """Create a temp variable with an explicit C++ type."""
424
+ self._counter += 1
425
+ temp_name = f"__tmp_{self._counter}"
426
+ self._pending.append((temp_name, cpp_type, init_expr, brace_init))
427
+ return temp_name
428
+
429
+ def declare_named(self, name: str, cpp_type: str, *,
430
+ init: str | None = None, brace_init: bool = False) -> None:
431
+ """Register a named pre-declaration (for walrus operator variables)."""
432
+ self._pending_named.append((name, cpp_type, init, brace_init))
433
+
434
+ def checkpoint(self) -> tuple[int, int]:
435
+ """Snapshot the current pending-temp queue lengths.
436
+
437
+ Pair with `rollback_to` to discard temps registered between the two
438
+ calls -- needed when emitting into a context that has no place to
439
+ flush declarations (e.g. a C++ member-initializer-list expression).
440
+ """
441
+ return (len(self._pending), len(self._pending_named))
442
+
443
+ def rollback_to(self, checkpoint: tuple[int, int]) -> bool:
444
+ """Discard temps registered after `checkpoint`. Returns True if any were dropped.
445
+
446
+ `_counter` is intentionally not rolled back: the caller may emit the same
447
+ expression again later (in a different context that can flush temps), and
448
+ reusing counter values would produce duplicate `__tmp_N` names. Skipping a
449
+ number is harmless -- temp names only need to be unique within a pass.
450
+ """
451
+ pending, pending_named = checkpoint
452
+ if len(self._pending) == pending and len(self._pending_named) == pending_named:
453
+ return False
454
+ del self._pending[pending:]
455
+ del self._pending_named[pending_named:]
456
+ return True
457
+
458
+ def flush(self, out: TextIO, indent: str) -> None:
459
+ """Emit any pending temp variable declarations."""
460
+ for name, cpp_type, init_val, brace_init in self._pending_named:
461
+ if init_val is not None:
462
+ out.write(f"{indent}{cpp_type} {name} = {init_val};\n")
463
+ elif brace_init:
464
+ out.write(f"{indent}{cpp_type} {name}{{}};\n")
465
+ else:
466
+ out.write(f"{indent}{cpp_type} {name};\n")
467
+ self._pending_named.clear()
468
+ for temp_name, type_cpp, init_expr, brace_init in self._pending:
469
+ if brace_init:
470
+ out.write(f"{indent}{type_cpp} {temp_name}{{{init_expr}}};\n")
471
+ else:
472
+ out.write(f"{indent}{type_cpp} {temp_name} = {init_expr};\n")
473
+ self._pending.clear()
474
+
475
+
476
+ class CodeGenError(Exception):
477
+ """Error during C++ code generation."""
478
+ def __init__(self, message: str, loc: SourceLocation | None = None,
479
+ filename: str | None = None):
480
+ self.message = message
481
+ self.loc = loc
482
+ self.filename = filename
483
+ super().__init__(message)
484
+
485
+ def format(self, filename: str = "<unknown>") -> str:
486
+ """Format error with file:line prefix."""
487
+ name = self.filename or filename
488
+ if self.loc:
489
+ return f"{name}:{self.loc.line}: error: {self.message}"
490
+ return f"{name}: error: {self.message}"
491
+
492
+
493
+ @dataclass
494
+ class CodeGenOptions:
495
+ """Options for C++ code generation."""
496
+ emit_source_comments: bool = False # Embed Python source as comments in generated C++
497
+ comment_line_numbers: bool = True # Include .py line numbers in source comments
498
+ no_main: bool = False # Skip main() generation, emit __tpy_main() only
499
+
500
+
501
+ class LocalCppForm(Enum):
502
+ """Coarse C++ representation of a local/param name.
503
+
504
+ The codegen tracks several parallel `set[str]` fields (`pointer_locals`,
505
+ `optional_locals`, `ptr_variant_locals`, `storage_form_tuple_locals`,
506
+ `storage_form_optional_locals`) plus a `current_func_params` lookup
507
+ for `Own[Union]` params. Every boundary-handling site (call arg,
508
+ return, var-decl init, rebind, tuple-unpack, etc.) needs to ask
509
+ "what shape is this name?" and route to the right lift / wrap
510
+ helper. `local_cpp_form` is the single classifier those sites consult.
511
+
512
+ Variants:
513
+ * `POINTER` -- `T*` / `const T*`. Pointer-form Optional or hoisted
514
+ non-value local. Membership in `pointer_locals` minus
515
+ `optional_locals`.
516
+ * `OPTIONAL_STORAGE` -- `std::optional<T>` storage form. `Own[Opt[T_ref]]`
517
+ params (also in `pointer_locals` for arrow field access). Lifts via
518
+ `tpy::optional_to_ptr` when consumed as a `T*` slot.
519
+ * `STORAGE_OPTIONAL` -- `std::optional<T>` storage form, but from
520
+ an iteration source (for-loop var iterating `list[P|None]` /
521
+ `dict[K, P|None]`, comprehension/genexpr unpack var bound from
522
+ a storage-form-tuple slot). Same lift as `OPTIONAL_STORAGE`
523
+ (`tpy::optional_to_ptr`); separate variant because these names
524
+ are NOT in `pointer_locals` (their access doesn't route through
525
+ `->`) and may be in `const_storage_form_optional_locals` for
526
+ const-source iteration.
527
+ * `VALUE_VARIANT` -- `std::variant<A, B>` storage form. `Own[Union nonvalue]`
528
+ params at the ABI. Lifts via `tpy::to_ptr_variant` when consumed
529
+ as a pointer-variant slot.
530
+ * `PTR_VARIANT` -- `std::variant<T*, ...>` borrow form. Non-value
531
+ union local already in pointer-variant shape; no lift needed.
532
+ * `STORAGE_TUPLE` -- `std::tuple<std::optional<T>, ...>` storage form.
533
+ For-loop variables iterating storage containers, locals initialized
534
+ from another storage-form source, `Own[tuple[T|None, ...]]` params.
535
+ Wraps via `tpy::tuple_to_pointer` when feeding pointer-form tuple
536
+ params/destructure targets.
537
+ * `VALUE` -- everything else (value types, T& ref-bound locals,
538
+ plain non-value locals rendered via T&).
539
+ """
540
+ POINTER = auto()
541
+ OPTIONAL_STORAGE = auto()
542
+ STORAGE_OPTIONAL = auto()
543
+ VALUE_VARIANT = auto()
544
+ PTR_VARIANT = auto()
545
+ STORAGE_TUPLE = auto()
546
+ VALUE = auto()
547
+
548
+
549
+ @dataclass
550
+ class LocalScopeSnap:
551
+ """Snapshot of the C++ local-variable declaration state inside a function body.
552
+
553
+ Covers every field that tracks which locals exist and what C++ representation
554
+ they use (pointer-local, const-indirect, movable, rebind slot), plus the
555
+ two isinstance-narrowing maps -- `narrowed_vars` (per-source-variable
556
+ alias bindings) and `declared_persistent_aliases` (the scope-global set of
557
+ persistent alias names, used by `_fresh_alias_local` for collision
558
+ avoidance). Both maps reference C++ locals that live only within the
559
+ block that declared them, so they must be revoked when the surrounding
560
+ C++ block closes. Used to restore scope between if/else branches so that
561
+ declarations inside one branch don't bleed into sibling branches.
562
+
563
+ If a new "what locals exist" field is added to CodeGenContext, add it here too.
564
+
565
+ Note: hoisted_vars and branch_hoisted_vars are NOT snapshotted -- they are
566
+ function-scoped accumulators. A stale entry from a prior branch's nested if
567
+ causes unnecessary hoisting but not incorrect code. Similarly,
568
+ frame_field_shadows is NOT snapshotted -- entries are either discarded by
569
+ the emit site that introduced them (for-loop iter var, scoped to the loop
570
+ body) or persist to function exit where `reset_scope` clears them (with-as
571
+ / tuple-unpack, which outlive their source statement in Python scoping).
572
+ Both lifetimes are correct relative to branch-snapshot boundaries.
573
+ """
574
+ declared_vars: set[str]
575
+ var_types: dict[str, TpyType]
576
+ local_scope_names: set[str]
577
+ pointer_locals: set[str]
578
+ optional_locals: set[str]
579
+ ptr_variant_locals: set[str]
580
+ const_indirect_locals: set[str]
581
+ storage_form_tuple_locals: set[str]
582
+ const_storage_form_tuple_locals: set[str]
583
+ storage_form_optional_locals: set[str]
584
+ const_storage_form_optional_locals: set[str]
585
+ movable_locals: set[str]
586
+ ref_bound_locals: set[str]
587
+ rebind_slots: dict[str, str]
588
+ plain_rebind_slots: set[str]
589
+ assign_narrowed_types: dict[str, 'TpyType']
590
+ narrowed_vars: dict[str, str]
591
+ declared_persistent_aliases: set[str]
592
+
593
+
594
+ @dataclass
595
+ class FinallyContext:
596
+ """Tracks an active try/finally (or with) block.
597
+
598
+ The finally body is emitted inline at each exit site: before any
599
+ fall-through, return, break, or continue out of the try block, and
600
+ inside the catch(...) wrapper for the throw path. The unified pattern
601
+ is `try { body } catch (...) { <finally>; throw; } <finally>;` with
602
+ explicit `<finally>;` calls inserted before any non-throw exit while
603
+ the frame is on the stack.
604
+
605
+ The same shape is used by the resumable-frame async codegen, where
606
+ each suspension's case body re-establishes the active try/finally
607
+ structure -- making the lowering shared across sync and async.
608
+ """
609
+ emit_finally: Callable[[TextIO, str], None]
610
+ """Emit the finally body inline at the given (out, indent)."""
611
+
612
+ terminates: bool
613
+ """True if the finally body's last reachable statement is raise/return.
614
+ When set, callers must suppress any trailing `throw;` / `return ...;`
615
+ after invoking emit_finally, since the body itself transferred control."""
616
+
617
+ loop_depth: int
618
+ """len(loop_else_labels) at frame push time. break/continue walk
619
+ finally frames whose loop_depth >= current loop count, since those
620
+ are the frames pushed inside the innermost active loop."""
621
+
622
+
623
+ @dataclass
624
+ class CodeGenContext:
625
+ """Shared state for C++ code generation."""
626
+
627
+ # --- Core ---
628
+ analyzer: SemanticAnalyzer
629
+ options: CodeGenOptions
630
+ module_name: str = "generated"
631
+ source_lines: list[str] = field(default_factory=list)
632
+ # Peer modules in the same import-graph SCC. When a `<peer>.hpp`
633
+ # would be included from this module's header (vs cpp file), the
634
+ # codegen swaps it for `<peer>_fwd.hpp` to break the cyclic
635
+ # complete-type include. Empty for non-cycle modules.
636
+ cycle_peers: frozenset[str] = field(default_factory=frozenset)
637
+
638
+ # --- Scope tracking ---
639
+ indent_level: int = 0
640
+ declared_vars: set[str] = field(default_factory=set)
641
+ var_types: dict[str, TpyType] = field(default_factory=dict)
642
+ local_scope_names: set[str] = field(default_factory=set)
643
+ nested_def_locals: set[str] = field(default_factory=set)
644
+ # Walrus pre-declarations already emitted (not snapshot/restored across branches)
645
+ walrus_pre_declared: set[str] = field(default_factory=set)
646
+ global_names: set[str] = field(default_factory=set)
647
+ current_ns: Namespace | None = None
648
+ in_method: bool = False
649
+ in_consuming_method: bool = False
650
+ in_property_getter: bool = False
651
+ # The NominalType of the record whose method is currently being emitted.
652
+ # Set by records.py / functions.py around method bodies and read by
653
+ # `lookup_var_type` so `self` resolves to the enclosing class type --
654
+ # enables `isinstance(self, Sub)` polymorphic-dispatch routing in codegen.
655
+ current_method_record_type: 'TpyType | None' = None
656
+ current_return_type: TpyType | None = None
657
+ # Generator yield type. Set at generator-body entry points (state-machine
658
+ # __next__, simple-for/simple-while inline lambdas) and read by all yield
659
+ # emission sites so they share one source of truth instead of threading
660
+ # the type through call signatures.
661
+ current_yield_type: TpyType | None = None
662
+ current_error_return: str | None = None
663
+ error_return_stmt_handled: bool = False
664
+ current_func_params: dict[str, TpyType] = field(default_factory=dict)
665
+ current_type_param_bounds: dict[str, TpyType] = field(default_factory=dict)
666
+ const_ref_params: set[str] = field(default_factory=set)
667
+ # Param names whose declared const-inferred surface goes deep -- inner
668
+ # tuple slots, pointer-form Optional inner, etc. Drives body codegen
669
+ # decisions for tuple unpack, alias propagation, call-site lowering.
670
+ # Subset of const_ref_params for ordinary T& params; also includes
671
+ # Optional/Tuple param names that const_ref_params doesn't track.
672
+ deep_const_borrow_params: set[str] = field(default_factory=set)
673
+
674
+ # --- Temporary variable management ---
675
+ temps: TempState = field(default_factory=TempState)
676
+
677
+ # --- Global declaration tracking (from `global x` statements) ---
678
+ global_declared_vars: set[str] = field(default_factory=set)
679
+
680
+ # --- Pointer-local tracking ---
681
+ pointer_locals: set[str] = field(default_factory=set)
682
+ # Locals whose C++ shape is std::optional<T> (storage form) rather than
683
+ # T* (pointer form). Two populations:
684
+ # - Non-value hoisted vars not reassigned (branch-emitted as
685
+ # std::optional<T> to avoid a separate T*+slot pair).
686
+ # - Own[OptionalType[P_ref]] params (rendered as std::optional<P>&&).
687
+ # Both also live in pointer_locals so the field-access dispatch routes
688
+ # via optional<T>::operator-> (correct arrow). The is_storage_form_-
689
+ # optional_source predicate deliberately excludes them; consumer sites
690
+ # that need an optional_to_ptr lift to feed a T* slot check
691
+ # optional_locals directly (_optional_pointer_form_value,
692
+ # _gen_optional_ptr_arg, the var-decl and rebind paths in statements.py).
693
+ optional_locals: set[str] = field(default_factory=set)
694
+ const_indirect_locals: set[str] = field(default_factory=set)
695
+ # Pre-bound polymorphic-isinstance cast locals for the C++17 if-init form
696
+ # (var -> ptr local name). Populated by `_gen_if` before emitting the
697
+ # condition; the isinstance bool-check and cast-and-cache extraction both
698
+ # consult this map and reuse the local instead of re-emitting dynamic_cast.
699
+ isinstance_init_locals: dict[str, str] = field(default_factory=dict)
700
+ # Same idea for deref-view narrowing through an owning wrapper (Box/Rc):
701
+ # keyed by the wrapper receiver name (e.g. `rc`), the value is the pre-bound
702
+ # `Sub*` local cast from the deref payload pointer, so the condition and the
703
+ # narrowed member accesses share one cast instead of re-deriving it.
704
+ deref_view_init_locals: dict[str, str] = field(default_factory=dict)
705
+ # Locals whose tuple type contains pointer-repr Optional but whose C++
706
+ # representation is the storage form (std::tuple<std::optional<T>, ...>):
707
+ # for-loop variables iterating storage-form containers, and locals
708
+ # initialized from a storage-form source (field, subscript, global).
709
+ # Read sites need a tuple_to_pointer wrap to flow into pointer-form
710
+ # tuple params/destructure targets.
711
+ storage_form_tuple_locals: set[str] = field(default_factory=set)
712
+ # Subset of storage_form_tuple_locals: locals iterated from a const-bound
713
+ # source (const list&, dict.values(), self.field in readonly method, ...).
714
+ # When unpacking these, the storage->pointer wrap must produce const slots
715
+ # because optional_to_ptr returns `const T*` from a const optional<T>&.
716
+ const_storage_form_tuple_locals: set[str] = field(default_factory=set)
717
+ # Locals whose C++ shape is `std::optional<T>` (storage form) because they
718
+ # bind a pointer-repr-Optional element of a storage-form source:
719
+ # * for-loop variable iterating `list[P|None]` / `dict[K, P|None]`
720
+ # * comprehension/genexpr unpack variable whose tuple slot is
721
+ # pointer-repr-Optional from a storage-form source
722
+ # `is_storage_form_optional_source(TpyName(...))` returns True for these;
723
+ # consumer sites needing `P*` insert `optional_to_ptr` based on it.
724
+ # Disjoint from `optional_locals` (which tracks Own[Opt[T_ref]] params).
725
+ storage_form_optional_locals: set[str] = field(default_factory=set)
726
+ # Subset of storage_form_optional_locals: locals bound from a const-bound
727
+ # source (mirror of const_storage_form_tuple_locals -- when the producing
728
+ # iteration yields const optional<T>&, the lift must produce const T*).
729
+ const_storage_form_optional_locals: set[str] = field(default_factory=set)
730
+ # Names where codegen has emitted a C++-scoped local declaration in the
731
+ # current scope that shadows a putative frame field of the same name.
732
+ # Sema treats every function-level local as a potential frame field
733
+ # (added to func.generator_locals -> generator_optional_fields), but
734
+ # for-loop iter vars on loops without internal yield/await are emitted
735
+ # as `auto&& it = *__beg_n` C++ locals instead. Body-emit consults this
736
+ # set to suppress the `(*name)` peel that would otherwise apply to a
737
+ # putative storage-form-optional frame field. NOT in LocalScopeSnap --
738
+ # the set is body-emit scoped; entries are added and discarded around
739
+ # the emit site that introduced the shadow.
740
+ frame_field_shadows: set[str] = field(default_factory=set)
741
+
742
+ # --- Pointer-variant locals (non-value union variables) ---
743
+ # Variables that are std::variant<T*...> instead of std::variant<T...>.
744
+ ptr_variant_locals: set[str] = field(default_factory=set)
745
+ pointer_globals: set[str] = field(default_factory=set)
746
+ final_globals: set[str] = field(default_factory=set)
747
+ slots: SlotState = field(default_factory=SlotState)
748
+ rebind_slots: dict[str, str] = field(default_factory=dict)
749
+ plain_rebind_slots: set[str] = field(default_factory=set)
750
+ reassigned_vars: set[str] = field(default_factory=set)
751
+ rvalue_reassigned_vars: set[str] = field(default_factory=set)
752
+ lvalue_reassigned_vars: set[str] = field(default_factory=set)
753
+
754
+ # --- Auto-move tracking (last-use -> std::move) ---
755
+ movable_locals: set[str] = field(default_factory=set)
756
+ sema_movable_locals: set[str] = field(default_factory=set)
757
+
758
+ # --- Reference-bound locals (T& aliases -- del must not move-sink) ---
759
+ # ref_bound_locals grows during codegen as T& decls are emitted -> in LocalScopeSnap.
760
+ # aliased_vars/alias_names are set once per function from prescan -> NOT in LocalScopeSnap.
761
+ ref_bound_locals: set[str] = field(default_factory=set)
762
+ aliased_vars: set[str] = field(default_factory=set)
763
+ alias_names: set[str] = field(default_factory=set)
764
+
765
+ # --- Move-through vars (lvalue alias promoted to owned via std::move) ---
766
+ move_through_vars: set[str] = field(default_factory=set)
767
+
768
+ # --- Hoisted variable tracking (scope escape phase 2) ---
769
+ hoisted_vars: set[str] = field(default_factory=set)
770
+ # Branch-hoisted pointer-locals: declared by _emit_branch_decls for if/match/try.
771
+ # Rvalue slots for these vars must go to pending_hoist_decls, not block scope.
772
+ branch_hoisted_vars: set[str] = field(default_factory=set)
773
+
774
+ # --- Comprehension-local variable names ---
775
+ # Loop variables inside comprehensions shadow globals during element
776
+ # expression codegen. Checked early in is_global_name().
777
+ comp_local_names: set[str] = field(default_factory=set)
778
+ pending_hoist_decls: list[str] = field(default_factory=list)
779
+
780
+ # True while generating container element expressions (dict/list/set/tuple
781
+ # literals, comprehension elements). Ternary codegen checks this to produce
782
+ # std::optional<T> instead of T*/nullptr -- containers always store
783
+ # std::optional<T>, while locals/returns use T*.
784
+ in_container_element: bool = False
785
+
786
+ # --- Union type narrowing (isinstance -> std::get) ---
787
+ narrowed_vars: dict[str, str] = field(default_factory=dict)
788
+ # Protocol isinstance narrowing: var -> narrowed protocol type.
789
+ # For `if isinstance(x, SomeProtocol):` on a union parameter, sema narrows x
790
+ # to the protocol type within the branch. No std::get extraction is emitted
791
+ # (the C++ template param is unchanged; the narrower concept constraint
792
+ # holds via `if constexpr`). This map lets get_resolved_type surface the
793
+ # narrower type to for-loop dispatch, `in` operator, etc.
794
+ protocol_narrowings: dict[str, 'TpyType'] = field(default_factory=dict)
795
+ # Assignment narrowing: var -> narrowed concrete type (for inline std::get at access points)
796
+ assign_narrowed_types: dict[str, 'TpyType'] = field(default_factory=dict)
797
+ # Literal type narrowing: var -> single-value LiteralType for dead branch elimination
798
+ literal_facts: dict[str, 'TpyType'] = field(default_factory=dict)
799
+ # Literal overload specialization: injected facts that survive reset_scope
800
+ # (managed by _gen_literal_specialized_function, same pattern as overload_param_types)
801
+ literal_overload_facts: dict[str, 'TpyType'] = field(default_factory=dict)
802
+
803
+ # --- @overload specialization ---
804
+ # When generating code for a specific @overload stub, maps parameter names
805
+ # to their concrete (non-union) types. Used for dead branch elimination:
806
+ # isinstance checks on specialized params resolve statically.
807
+ overload_param_types: dict[str, 'TpyType'] = field(default_factory=dict)
808
+ # For short-arity stubs, the missing impl params that must be emitted as
809
+ # locals (with their default expressions) at the start of the body.
810
+ # Each entry: (param_name, param_type, default_expr). Consumed by
811
+ # StatementGenerator.gen_body right after the opening brace and then
812
+ # cleared so inner bodies don't re-emit them.
813
+ overload_missing_param_locals: list[tuple[str, 'TpyType', object]] = field(default_factory=list)
814
+
815
+ # --- Iterator loop counter ---
816
+ iter_counter: int = 0
817
+
818
+ # --- Tuple unpacking counter ---
819
+ unpack_counter: int = 0
820
+
821
+ # --- try/except ---
822
+ try_except_counter: int = 0
823
+ # When set, we're inside a try body -- error_return calls should goto this label
824
+ try_except_label: str | None = None
825
+ # When set (except E as e), error_return calls should move error into this var before goto
826
+ try_except_err_opt: str | None = None
827
+ in_except_tier: Literal["return", "throw"] | None = None
828
+
829
+ # --- finally (inline emit pattern) ---
830
+ # Stack of active try/finally (and with) blocks. Codegen invokes
831
+ # frame.emit_finally inline before each non-throw exit (return/break/
832
+ # continue/fall-through) and inside each try block's catch(...) wrapper.
833
+ # No goto labels, no shared __retval variable.
834
+ finally_stack: list[FinallyContext] = field(default_factory=list)
835
+
836
+ # --- with statement ---
837
+ with_counter: int = 0
838
+ # Variables pre-declared by _emit_branch_decls for for-loop hoisting
839
+ loop_hoisted_vars: set[str] = field(default_factory=set)
840
+
841
+ # --- Match/case label counter (for goto-based guard fallthrough) ---
842
+ match_counter: int = 0
843
+
844
+ # --- Generator function codegen ---
845
+ in_generator_body: bool = False
846
+ generator_field_names: set[str] = field(default_factory=set)
847
+ # Fields read through an outer wrap (`(*name)` deref) -- the union
848
+ # of user locals stored as `tpy::frame_slot<T>` and synthetic for-
849
+ # loop / async-with / async-for / try-finally fields still stored
850
+ # as `std::optional<T>`. Read sites can use the same `(*name)`
851
+ # access for both.
852
+ generator_optional_fields: set[str] = field(default_factory=set)
853
+ # Subset of generator_optional_fields whose C++ slot is the new
854
+ # `tpy::frame_slot<T>` (user locals from `func.generator_locals`,
855
+ # non-value, non-pointer-form). Writes here must go through
856
+ # `.emplace(value)` -- the helper has no operator= for arbitrary T.
857
+ generator_frame_slot_locals: set[str] = field(default_factory=set)
858
+ # Subset of pointer_locals that are borrow-form for-loop vars (a `T*`
859
+ # aliasing a live container element). Yielding one by name needs a deref
860
+ # to the value yield slot -- but pointer-repr Optional/Union locals (also
861
+ # in pointer_locals) must NOT be deref'd that way, hence a dedicated set.
862
+ generator_borrow_form_loop_vars: set[str] = field(default_factory=set)
863
+ # For-loops with yields in state machine generators: keyed by id(TpyForEach)
864
+ # Values are GeneratorForInfo (not imported here to avoid circular dep)
865
+ generator_for_loop_info: dict[int, object] = field(default_factory=dict)
866
+ # When generating a generator method's __next__() body, self -> __self
867
+ generator_self_ref: str | None = None
868
+
869
+ # --- Async coroutine function codegen ---
870
+ # When generating an `async def` body inside its struct's poll() method,
871
+ # `return v` is rewritten to `__state = <done>; return Poll<T>::ready(v);`.
872
+ # The flag piggybacks on in_generator_body for the field-rewrite path
873
+ # (locals -> this->field) -- both share the resumable-frame shape -- but
874
+ # has its own return-rewrite handling in statements.py.
875
+ in_async_coro_body: bool = False
876
+ async_coro_return_cpp: str | None = None # C++ return type for Poll<T>::ready
877
+ async_coro_done_state: str | None = None # name of the DONE state enumerator
878
+ # Generator body lowered onto the resumable frame (yield -> __next__ ->
879
+ # expected<T, StopIteration>). Distinct from in_generator_body, which is
880
+ # the legacy goto-label generator codegen: there a bare `return` emits
881
+ # `goto __done`, but the resumable while/switch has no such label, so a
882
+ # bare `return` / fall-off-end lowers to `__state = S_DONE; return
883
+ # make_unexpected(StopIteration{})` instead (see statements.py).
884
+ in_generator_resumable_body: bool = False
885
+ generator_resumable_done_state: str | None = None
886
+ # Set when emitting a helper-based finally body for a generator on the
887
+ # resumable frame. `return` here sets `this->__finally_stop = true` and
888
+ # void-returns; callers check the flag and emit StopIteration.
889
+ in_generator_finally_helper: bool = False
890
+ # Resumable-frame `match` decomposition (H1): when a suspending `match`
891
+ # is emitted, the existing `gen_match` dispatch is reused unchanged, but
892
+ # each arm BODY is routed back through the resumable walker instead of
893
+ # emitted inline. `resumable_arm_emitter(arm_bb)` emits one arm's body
894
+ # via the state machine; `resumable_arm_bb_by_body` maps `id(case.body)`
895
+ # to its arm BB. Consulted in the single shared `_emit_case_body`
896
+ # chokepoint. None outside a suspending-match emit.
897
+ resumable_arm_emitter: 'Callable[[int], None] | None' = None
898
+ resumable_arm_bb_by_body: dict[int, int] = field(default_factory=dict)
899
+ # Whether the `match` currently being emitted has an lvalue subject (set
900
+ # by `gen_match`). A non-lvalue subject is bound to a dispatch-local copy,
901
+ # so a pointer-form (pointer-repr `Optional`) arm binding would alias that
902
+ # copy and dangle across a suspension -- the resumable binding emit rejects
903
+ # that combination. Default True (lvalue) for non-match / non-resumable.
904
+ resumable_match_subject_is_lvalue: bool = True
905
+ # Source location of the `match` currently being emitted, used to locate
906
+ # the resumable binding-emit reject (the binding emit is deep in the
907
+ # dispatch and has no stmt in hand otherwise). Set by `gen_match`.
908
+ resumable_match_loc: 'SourceLocation | None' = None
909
+ # True iff the current generator's struct has a `__finally_stop` field
910
+ # (i.e. at least one helper-based finally body contains a `return`).
911
+ # Controls whether _emit_finally_helper_call appends the stop check.
912
+ generator_has_finally_stop: bool = False
913
+ # Pending-return routing inside a CFG-based finally region
914
+ #: when set, `return v` inside the active case saves `v`
915
+ # to the slot, sets the flag, walks finally frames up to
916
+ # `async_pending_return_boundary` (the position in finally_stack
917
+ # belonging to regions OUTSIDE the CFG-based finally -- they're
918
+ # walked later by AsyncFinallyExit), then transitions state to
919
+ # the finally entry. `slot` is None for void async defs.
920
+ async_pending_return_flag: str | None = None
921
+ async_pending_return_slot: str | None = None
922
+ async_pending_return_target_state: str | None = None
923
+ async_pending_return_boundary: int = 0
924
+
925
+ # --- for/else, while/else label stack ---
926
+ # When generating a loop with an else clause, the goto label name is
927
+ # pushed here so TpyBreak codegen can emit `goto label` instead of `break`.
928
+ loop_else_labels: list[str] = field(default_factory=list)
929
+
930
+ # --- Cross-module import tracking ---
931
+ user_module_imports: set[str] = field(default_factory=set)
932
+ all_user_modules: set[str] = field(default_factory=set)
933
+ implicit_stdlib_modules: set[str] = field(default_factory=set)
934
+ macro_dep_modules: set[str] = field(default_factory=set)
935
+ emitted_tpy_inits: set[str] = field(default_factory=set)
936
+ top_level_decls: dict[str, int] = field(default_factory=dict)
937
+ current_stmt_line: int = 0
938
+
939
+ # --- Native global variable imports ---
940
+ # TODO: replace with dict[str, NativeGlobalInfo] holding c_name, linkage, var_type
941
+ # instead of a flat name->name mapping (the TpyVarDecl nodes already carry this)
942
+ native_global_names: dict[str, str] = field(default_factory=dict)
943
+
944
+ def iter_imported_recursive_unions(self) -> 'Iterator[RecursiveUnionInfo]':
945
+ """Yield each cross-module recursive alias once.
946
+
947
+ Skips aliases whose origin is the current module. The compiler-wide
948
+ index contains each alias under both its full member tuple and the
949
+ non-None subset, so id-based de-dup is needed -- both keys point
950
+ at the same RecursiveUnionInfo instance produced by `_register`.
951
+ """
952
+ compiler = get_current_compiler()
953
+ if compiler is None:
954
+ return
955
+ seen: set[int] = set()
956
+ for info in compiler.union_wrapper_index.values():
957
+ if info.origin == self.module_name:
958
+ continue
959
+ if id(info) in seen:
960
+ continue
961
+ seen.add(id(info))
962
+ yield info
963
+
964
+ def is_ptr_variant_union(self, typ: 'TpyType') -> bool:
965
+ """Check if a union type uses pointer-variant representation.
966
+
967
+ Returns True for non-value unions (e.g. Dog | Cat with records)
968
+ that are NOT recursive union aliases. Recursive unions use wrapper
969
+ structs which are value types, so they skip pointer-variant form.
970
+ """
971
+ from ..typesys import UnionType
972
+ return (isinstance(typ, UnionType)
973
+ and typ.uses_pointer_repr()
974
+ and not typ.needs_wrapper())
975
+
976
+ def is_ptr_variant_source(self, expr: TpyExpr) -> bool:
977
+ """Check if an expression produces a pointer variant (vs value variant).
978
+
979
+ Pointer-variant sources: ptr_variant locals, union params, function
980
+ calls returning non-value unions. Value-variant sources: constructors,
981
+ Own returns, field access, container subscript, bare alternative values.
982
+
983
+ Note: only `ReadonlyType` is unwrapped on params. `OwnType[A | B]`
984
+ params lower to a value-variant `std::variant<A, B>&&` (the caller
985
+ gave up ownership), so they should NOT take the ptr-variant path.
986
+ """
987
+ if isinstance(expr, TpyCoerce):
988
+ return self.is_ptr_variant_source(expr.expr)
989
+ if isinstance(expr, TpyName):
990
+ if expr.name in self.ptr_variant_locals:
991
+ return True
992
+ # __init__ codegen sets current_func_params but doesn't populate
993
+ # ptr_variant_locals (only the regular function-body path does).
994
+ # Consult the param table directly so ctor-init MIL conversions
995
+ # match the body-assignment behaviour.
996
+ ptype = self.current_func_params.get(expr.name)
997
+ if ptype is not None and self.is_ptr_variant_union(unwrap_readonly(ptype)):
998
+ return True
999
+ return False
1000
+ if isinstance(expr, (TpyCall, TpyMethodCall)):
1001
+ fi = expr.resolved_function_info
1002
+ if fi is not None and self.is_ptr_variant_union(fi.return_type):
1003
+ return True
1004
+ return False
1005
+
1006
+ def reset_scope(self) -> None:
1007
+ """Reset all per-scope state for a new function/method/module-init body."""
1008
+ self.declared_vars = set()
1009
+ self.var_types = {}
1010
+ self.local_scope_names = set()
1011
+ self.nested_def_locals = set()
1012
+ self.global_declared_vars = set()
1013
+ self.pointer_locals = set()
1014
+ self.optional_locals = set()
1015
+ self.ptr_variant_locals = set()
1016
+ self.const_indirect_locals = set()
1017
+ self.storage_form_tuple_locals = set()
1018
+ self.const_storage_form_tuple_locals = set()
1019
+ self.storage_form_optional_locals = set()
1020
+ self.const_storage_form_optional_locals = set()
1021
+ self.slots.reset()
1022
+ self.rebind_slots = {}
1023
+ self.plain_rebind_slots = set()
1024
+ self.reassigned_vars = set()
1025
+ self.rvalue_reassigned_vars = set()
1026
+ self.lvalue_reassigned_vars = set()
1027
+ self.movable_locals = set()
1028
+ self.ref_bound_locals = set()
1029
+ self.aliased_vars = set()
1030
+ self.alias_names = set()
1031
+ self.move_through_vars = set()
1032
+ self.hoisted_vars = set()
1033
+ self.branch_hoisted_vars = set()
1034
+ self.comp_local_names = set()
1035
+ self.frame_field_shadows = set()
1036
+ self.pending_hoist_decls = []
1037
+ self.current_ns = None
1038
+ self.indent_level = 0
1039
+ self.current_return_type = None
1040
+ self.current_yield_type = None
1041
+ self.current_error_return = None
1042
+ self.error_return_stmt_handled = False
1043
+ self.current_func_params = {}
1044
+ self.current_type_param_bounds = {}
1045
+ self.in_method = False
1046
+ self.narrowed_vars = {}
1047
+ # All persistent cast-and-cache aliases declared in the current C++
1048
+ # scope. narrowed_vars holds only the *most recent* alias per source
1049
+ # variable, so it loses earlier aliases after a bump (`__p` -> `__p_2`
1050
+ # rewrites narrowed_vars[p] and the `__p` declaration becomes
1051
+ # untracked even though it's still live). This set retains every
1052
+ # emitted alias name in the current scope so `_fresh_alias_local`'s
1053
+ # collision check spans the full history.
1054
+ self.declared_persistent_aliases: set[str] = set()
1055
+ self.protocol_narrowings = {}
1056
+ self.assign_narrowed_types = {}
1057
+ self.literal_facts = {}
1058
+ self.walrus_pre_declared = set()
1059
+ self.overload_terminated = False
1060
+ # Note: overload_param_types and literal_overload_facts are NOT reset
1061
+ # here -- they're managed by the caller (set before gen_body, cleared
1062
+ # in a finally block).
1063
+ self.match_counter = 0
1064
+ self.iter_counter = 0
1065
+ self.unpack_counter = 0
1066
+ self.loop_else_labels = []
1067
+ self.loop_hoisted_vars = set()
1068
+ self.finally_stack = []
1069
+
1070
+ def indent(self) -> str:
1071
+ """Get current indentation string."""
1072
+ return INDENT * self.indent_level
1073
+
1074
+ def snapshot_local_scope(self) -> LocalScopeSnap:
1075
+ """Snapshot the local-variable declaration state (see LocalScopeSnap)."""
1076
+ return LocalScopeSnap(
1077
+ declared_vars=self.declared_vars.copy(),
1078
+ var_types=dict(self.var_types),
1079
+ local_scope_names=self.local_scope_names.copy(),
1080
+ pointer_locals=self.pointer_locals.copy(),
1081
+ optional_locals=self.optional_locals.copy(),
1082
+ ptr_variant_locals=self.ptr_variant_locals.copy(),
1083
+ const_indirect_locals=self.const_indirect_locals.copy(),
1084
+ storage_form_tuple_locals=self.storage_form_tuple_locals.copy(),
1085
+ const_storage_form_tuple_locals=self.const_storage_form_tuple_locals.copy(),
1086
+ storage_form_optional_locals=self.storage_form_optional_locals.copy(),
1087
+ const_storage_form_optional_locals=self.const_storage_form_optional_locals.copy(),
1088
+ movable_locals=self.movable_locals.copy(),
1089
+ ref_bound_locals=self.ref_bound_locals.copy(),
1090
+ rebind_slots=dict(self.rebind_slots),
1091
+ plain_rebind_slots=self.plain_rebind_slots.copy(),
1092
+ assign_narrowed_types=dict(self.assign_narrowed_types),
1093
+ narrowed_vars=dict(self.narrowed_vars),
1094
+ declared_persistent_aliases=self.declared_persistent_aliases.copy(),
1095
+ )
1096
+
1097
+ def restore_local_scope(self, snap: LocalScopeSnap) -> None:
1098
+ """Restore local-variable declaration state from a snapshot."""
1099
+ self.declared_vars = snap.declared_vars.copy()
1100
+ self.var_types = dict(snap.var_types)
1101
+ self.local_scope_names = snap.local_scope_names.copy()
1102
+ self.pointer_locals = snap.pointer_locals.copy()
1103
+ self.optional_locals = snap.optional_locals.copy()
1104
+ self.ptr_variant_locals = snap.ptr_variant_locals.copy()
1105
+ self.const_indirect_locals = snap.const_indirect_locals.copy()
1106
+ self.storage_form_tuple_locals = snap.storage_form_tuple_locals.copy()
1107
+ self.const_storage_form_tuple_locals = snap.const_storage_form_tuple_locals.copy()
1108
+ self.storage_form_optional_locals = snap.storage_form_optional_locals.copy()
1109
+ self.const_storage_form_optional_locals = snap.const_storage_form_optional_locals.copy()
1110
+ self.movable_locals = snap.movable_locals.copy()
1111
+ self.ref_bound_locals = snap.ref_bound_locals.copy()
1112
+ self.rebind_slots = dict(snap.rebind_slots)
1113
+ self.plain_rebind_slots = snap.plain_rebind_slots.copy()
1114
+ self.assign_narrowed_types = dict(snap.assign_narrowed_types)
1115
+ self.narrowed_vars = dict(snap.narrowed_vars)
1116
+ self.declared_persistent_aliases = snap.declared_persistent_aliases.copy()
1117
+
1118
+ def restore_narrowed_vars(self, saved: dict[str, str | None]) -> None:
1119
+ """Restore narrowed_vars after a branch block."""
1120
+ for var_name, prev in saved.items():
1121
+ if prev is not None:
1122
+ self.narrowed_vars[var_name] = prev
1123
+ else:
1124
+ self.narrowed_vars.pop(var_name, None)
1125
+
1126
+ def save_protocol_narrowings(self) -> dict[str, 'TpyType']:
1127
+ """Snapshot protocol_narrowings before entering a branch."""
1128
+ return dict(self.protocol_narrowings)
1129
+
1130
+ def restore_protocol_narrowings(self, saved: dict[str, 'TpyType']) -> None:
1131
+ """Restore protocol_narrowings after a branch block."""
1132
+ self.protocol_narrowings = dict(saved)
1133
+
1134
+ def save_literal_facts(self) -> dict[str, 'TpyType']:
1135
+ """Snapshot literal_facts before entering a branch."""
1136
+ return dict(self.literal_facts)
1137
+
1138
+ def restore_literal_facts(self, saved: dict[str, 'TpyType']) -> None:
1139
+ """Restore literal_facts after a branch block."""
1140
+ self.literal_facts = saved
1141
+
1142
+ def any_ancestor_has_del(self, record_name: str) -> bool:
1143
+ """Check if any ancestor of the named record has __del__."""
1144
+ record_info = self.analyzer.registry.get_record(record_name)
1145
+ if record_info is None:
1146
+ return False
1147
+ return any(
1148
+ anc_rec.has_del
1149
+ for anc_rec in self.analyzer.registry.iter_ancestor_records(record_info)
1150
+ )
1151
+
1152
+ def record_or_ancestor_has_del(self, record_name: str) -> bool:
1153
+ """Check if the named record or any ancestor has __del__."""
1154
+ record_info = self.analyzer.registry.get_record(record_name)
1155
+ if record_info is None:
1156
+ return False
1157
+ if record_info.has_del:
1158
+ return True
1159
+ return self.any_ancestor_has_del(record_name)
1160
+
1161
+ def _write_source_comment(self, out: TextIO, line_no: int, source: str, indent: str = "") -> None:
1162
+ """Write a source comment line, optionally including the .py line number."""
1163
+ source = source.lstrip()
1164
+ if self.options.comment_line_numbers:
1165
+ out.write(f"{indent}// {line_no}: {source}\n")
1166
+ else:
1167
+ out.write(f"{indent}// {source}\n")
1168
+
1169
+ def emit_source_comment(self, out: TextIO, loc: SourceLocation | None, indent: str = "") -> None:
1170
+ """Emit the original Python source line(s) as a comment if enabled.
1171
+
1172
+ Walks subsequent lines until the logical line ends -- detected as the
1173
+ first `NEWLINE` token at bracket-depth zero. Lets multi-line
1174
+ signatures (`def f(\\n a: T,\\n) -> T:`) and other paren-continued
1175
+ expressions render their full source instead of just the opening line.
1176
+ """
1177
+ if not self.options.emit_source_comments:
1178
+ return
1179
+ if loc is None:
1180
+ return
1181
+ start_idx = loc.line - 1
1182
+ if not (0 <= start_idx < len(self.source_lines)):
1183
+ return
1184
+ end_idx = start_idx
1185
+ # Tokenize from this line forward to find the logical-line boundary.
1186
+ # 32-line cap is a safety bound; real signatures fit easily. Track
1187
+ # paren depth via OP tokens so the boundary is the first NEWLINE/NL
1188
+ # at depth 0 -- `NEWLINE` ends a statement; `NL` at depth 0 ends a
1189
+ # comment-only or blank line (and we shouldn't pull the next stmt's
1190
+ # source into this comment block).
1191
+ snippet = "".join(
1192
+ line if line.endswith("\n") else line + "\n"
1193
+ for line in self.source_lines[start_idx:start_idx + 32]
1194
+ )
1195
+ try:
1196
+ import io
1197
+ import tokenize
1198
+ depth = 0
1199
+ for tok in tokenize.generate_tokens(io.StringIO(snippet).readline):
1200
+ if tok.type == tokenize.OP:
1201
+ if tok.string in "([{":
1202
+ depth += 1
1203
+ elif tok.string in ")]}":
1204
+ depth -= 1
1205
+ elif tok.type in (tokenize.NEWLINE, tokenize.NL) and depth <= 0:
1206
+ end_idx = start_idx + tok.start[0] - 1
1207
+ break
1208
+ except (tokenize.TokenError, IndentationError):
1209
+ pass # Fall back: just the start line.
1210
+ end_idx = min(end_idx, len(self.source_lines) - 1)
1211
+ for i in range(start_idx, end_idx + 1):
1212
+ self._write_source_comment(out, i + 1, self.source_lines[i].rstrip(), indent)
1213
+
1214
+ def emit_else_comment(self, out: TextIO, orelse: list, indent: str = "") -> None:
1215
+ """Emit the 'else:' source line as a comment.
1216
+
1217
+ Scans backward from the first orelse statement, skipping blank lines
1218
+ and comment lines. Stops on any other non-empty line that is not 'else:'.
1219
+ """
1220
+ if not self.options.emit_source_comments:
1221
+ return
1222
+ if not orelse or not hasattr(orelse[0], 'loc') or orelse[0].loc is None:
1223
+ return
1224
+ first_line = orelse[0].loc.line
1225
+ for line_num in range(first_line - 1, 0, -1):
1226
+ line_idx = line_num - 1
1227
+ if 0 <= line_idx < len(self.source_lines):
1228
+ stripped = self.source_lines[line_idx].strip()
1229
+ if stripped.startswith('else:'):
1230
+ self._write_source_comment(out, line_num, self.source_lines[line_idx].rstrip(), indent)
1231
+ return
1232
+ if stripped and not stripped.startswith('#'):
1233
+ return
1234
+
1235
+ def emit_inline_comments(self, out: TextIO, loc: SourceLocation | None, indent: str = "") -> None:
1236
+ """Emit Python comment lines immediately preceding a statement.
1237
+
1238
+ Walks backwards from the line before loc, skipping blank lines,
1239
+ then collecting consecutive comment lines (#...) at the same or
1240
+ shallower indentation as the current statement (deeper-indented
1241
+ comments belong to inner blocks and are handled by
1242
+ emit_block_trailing_comments).
1243
+ Emits them in source order.
1244
+ """
1245
+ if not self.options.emit_source_comments:
1246
+ return
1247
+ if loc is None:
1248
+ return
1249
+ stmt_line = self.source_lines[loc.line - 1]
1250
+ stmt_indent = len(stmt_line) - len(stmt_line.lstrip())
1251
+ collected: list[tuple[int, str]] = []
1252
+ idx = loc.line - 2 # 0-indexed line before current statement
1253
+ # Skip blank lines
1254
+ while idx >= 0 and not self.source_lines[idx].strip():
1255
+ idx -= 1
1256
+ # Collect consecutive comment lines at same or shallower indentation
1257
+ while idx >= 0:
1258
+ stripped = self.source_lines[idx].strip()
1259
+ if stripped.startswith("#"):
1260
+ line_indent = len(self.source_lines[idx]) - len(self.source_lines[idx].lstrip())
1261
+ if line_indent > stmt_indent:
1262
+ break
1263
+ collected.append((idx + 1, self.source_lines[idx].rstrip()))
1264
+ idx -= 1
1265
+ else:
1266
+ break
1267
+ # Emit in source order
1268
+ for line_no, source_line in reversed(collected):
1269
+ self._write_source_comment(out, line_no, source_line, indent)
1270
+
1271
+ def emit_block_trailing_comments(self, out: TextIO, body: list, indent: str = "") -> None:
1272
+ """Emit trailing comment lines after the last statement in a block.
1273
+
1274
+ Scans forward from the last statement's line, emitting Python comment
1275
+ lines that maintain the same or deeper indentation. Stops at
1276
+ non-comment code or dedented lines.
1277
+ """
1278
+ if not self.options.emit_source_comments:
1279
+ return
1280
+ if not body:
1281
+ return
1282
+ last_stmt = body[-1]
1283
+ if not hasattr(last_stmt, 'loc') or last_stmt.loc is None:
1284
+ return
1285
+ last_line = last_stmt.loc.line
1286
+ # Determine expected indentation from the last statement's source
1287
+ ref_idx = last_line - 1
1288
+ if ref_idx < 0 or ref_idx >= len(self.source_lines):
1289
+ return
1290
+ ref_line = self.source_lines[ref_idx]
1291
+ block_indent = len(ref_line) - len(ref_line.lstrip())
1292
+
1293
+ line_no = last_line + 1
1294
+ while line_no <= len(self.source_lines):
1295
+ line_idx = line_no - 1
1296
+ source_line = self.source_lines[line_idx]
1297
+ stripped = source_line.strip()
1298
+ if not stripped:
1299
+ line_no += 1
1300
+ continue
1301
+ line_indent = len(source_line) - len(source_line.lstrip())
1302
+ if line_indent < block_indent:
1303
+ break
1304
+ if stripped.startswith("#"):
1305
+ self._write_source_comment(out, line_no, source_line.rstrip(), indent)
1306
+ line_no += 1
1307
+ else:
1308
+ break
1309
+
1310
+ def emit_preceding_comments(self, out: TextIO, loc: SourceLocation | None, indent: str = "") -> None:
1311
+ """Emit comments and decorators preceding a definition as C++ comments.
1312
+
1313
+ Walks backwards from the line before loc, skipping blank lines,
1314
+ collecting decorator lines (@...) and comment lines (#...) at the
1315
+ same indentation as the definition. Deeper-indented comments belong
1316
+ to the body of a preceding definition and are skipped.
1317
+ Emits them in source order.
1318
+ """
1319
+ if not self.options.emit_source_comments:
1320
+ return
1321
+ if loc is None:
1322
+ return
1323
+ # Determine the definition's indentation
1324
+ def_line = self.source_lines[loc.line - 1]
1325
+ def_indent = len(def_line) - len(def_line.lstrip())
1326
+ collected: list[tuple[int, str]] = []
1327
+ idx = loc.line - 2 # 0-indexed line before definition
1328
+ # Skip blank lines between definition and block above
1329
+ while idx >= 0 and not self.source_lines[idx].strip():
1330
+ idx -= 1
1331
+ # Collect decorator lines
1332
+ while idx >= 0:
1333
+ stripped = self.source_lines[idx].strip()
1334
+ if stripped.startswith("@"):
1335
+ collected.append((idx + 1, self.source_lines[idx].rstrip()))
1336
+ idx -= 1
1337
+ else:
1338
+ break
1339
+ # Skip blank lines between decorators and comments
1340
+ while idx >= 0 and not self.source_lines[idx].strip():
1341
+ idx -= 1
1342
+ # Collect comment lines at same indentation as the definition
1343
+ while idx >= 0:
1344
+ stripped = self.source_lines[idx].strip()
1345
+ if stripped.startswith("#"):
1346
+ line_indent = len(self.source_lines[idx]) - len(self.source_lines[idx].lstrip())
1347
+ if line_indent > def_indent:
1348
+ break
1349
+ collected.append((idx + 1, self.source_lines[idx].rstrip()))
1350
+ idx -= 1
1351
+ else:
1352
+ break
1353
+ # Emit in source order (collected is reversed)
1354
+ for line_no, source_line in reversed(collected):
1355
+ self._write_source_comment(out, line_no, source_line, indent)
1356
+
1357
+ def lookup_var_type(self, var_name: str) -> 'TpyType | None':
1358
+ """Resolve a variable's declared type in any visible scope.
1359
+
1360
+ Function-locals and parameters live in `var_types`; module-level
1361
+ globals are bound on the analyzer's `global_ns`. `self` inside a
1362
+ method body resolves to the enclosing record's type via
1363
+ `current_method_record_type`. Returns None when the name isn't
1364
+ found anywhere.
1365
+ """
1366
+ typ = self.var_types.get(var_name)
1367
+ if typ is not None:
1368
+ return typ
1369
+ if (var_name == "self"
1370
+ and self.in_method
1371
+ and "self" not in self.current_func_params
1372
+ and self.current_method_record_type is not None):
1373
+ return self.current_method_record_type
1374
+ global_ns = self.analyzer.ctx.global_ns
1375
+ if global_ns is not None:
1376
+ binding = global_ns.lookup(var_name)
1377
+ if binding is not None and binding.type is not None:
1378
+ return binding.type
1379
+ return None
1380
+
1381
+ def is_global_name(self, expr: TpyExpr) -> bool:
1382
+ """Check if expression is a reference to a global variable.
1383
+
1384
+ Returns False if the name is shadowed by a local variable or parameter.
1385
+ """
1386
+ if not isinstance(expr, TpyName):
1387
+ return False
1388
+
1389
+ if expr.name in self.comp_local_names:
1390
+ return False
1391
+
1392
+ # Use namespace if available
1393
+ if self.current_ns:
1394
+ # Check if it's a global variable
1395
+ global_binding = self.analyzer.global_ns.lookup_local(expr.name)
1396
+ if global_binding is None or global_binding.kind != BindingKind.VARIABLE:
1397
+ return False # Not a global variable
1398
+
1399
+ # Check if locally shadowed (only if we have a local namespace, not global_ns itself)
1400
+ if self.current_ns is not self.analyzer.global_ns:
1401
+ # Traverse local namespace chain (up to but not including global_ns)
1402
+ ns: Namespace | None = self.current_ns
1403
+ while ns is not None and ns is not self.analyzer.global_ns:
1404
+ if ns.lookup_local(expr.name):
1405
+ return False # Locally shadowed
1406
+ ns = ns.parent
1407
+
1408
+ return True
1409
+
1410
+ # Fallback: use old tracking
1411
+ # local_scope_names contains function params and locally-declared variables
1412
+ return expr.name in self.global_names and expr.name not in self.local_scope_names
1413
+
1414
+ def is_pointer_local(self, expr: TpyExpr) -> bool:
1415
+ """Check if expression is a reference to a pointer-local variable."""
1416
+ if not isinstance(expr, TpyName):
1417
+ return False
1418
+ return expr.name in self.pointer_locals
1419
+
1420
+ def is_storage_form_optional_source(self, expr: TpyExpr) -> bool:
1421
+ """True when expr renders as a storage-form Optional[T] lvalue
1422
+ (`std::optional<T>&` for a non-value T) -- i.e. needs the same
1423
+ downstream handling at consumer sites as a record's storage-form
1424
+ Optional field: `optional_to_ptr` at borrow sites, double-deref
1425
+ at narrowing sites, `ptr_to_optional` at assignments.
1426
+
1427
+ Covers three source shapes that produce the same C++ shape:
1428
+
1429
+ * TpyFieldAccess on a storage-form Optional[T] field of a
1430
+ record (`obj.maybe_p` where the field is stored as
1431
+ `std::optional<T>`).
1432
+ * TpySubscript yielding a pointer-repr Optional element of a
1433
+ storage-form container (`pairs[i]` where
1434
+ `pairs: list[P|None]`, `d[k]` where `d: dict[K, P|None]`).
1435
+ `__getitem__` returns `std::optional<P>` (storage form).
1436
+ * TpyName whose `local_cpp_form` is `STORAGE_OPTIONAL` -- a
1437
+ for-loop variable iterating such a container, or a
1438
+ comprehension/genexpr unpack variable bound from a
1439
+ storage-form-tuple slot whose element type is pointer-repr
1440
+ Optional. Routed through the classifier rather than direct
1441
+ set lookup so all name-keyed shape dispatch goes through one
1442
+ place; the backing set is `storage_form_optional_locals`.
1443
+
1444
+ Note: `optional_locals` names (`Own[OptionalType[P_ref]]` params
1445
+ rendered as `std::optional<P>&&`) are NOT included. They share
1446
+ the storage-form C++ shape but are also in `pointer_locals`,
1447
+ and the downstream field-access dispatch already routes via
1448
+ `optional<T>::operator->`. The consumer sites that DO need a
1449
+ `optional_to_ptr` lift for these names (call-arg, return into
1450
+ pointer-form, pointer-local rebind) check `optional_locals`
1451
+ directly.
1452
+ """
1453
+ if isinstance(expr, TpyFieldAccess):
1454
+ val_type = self.get_expr_type(expr)
1455
+ return (isinstance(val_type, OptionalType)
1456
+ and val_type.uses_pointer_repr())
1457
+ if isinstance(expr, TpySubscript):
1458
+ val_type = self.get_expr_type(expr)
1459
+ if not (isinstance(val_type, OptionalType)
1460
+ and val_type.uses_pointer_repr()):
1461
+ return False
1462
+ # Tuple-subscript codegen pre-lifts via `optional_to_ptr` (see
1463
+ # `_gen_subscript` tuple branch) when the object is a storage-form
1464
+ # tuple source. The rendered expression is already `T*`, so
1465
+ # consumers must NOT lift again.
1466
+ obj_type = self.get_expr_type(expr.obj)
1467
+ if obj_type is not None and isinstance(unwrap_qualifiers(obj_type), TupleType):
1468
+ return False
1469
+ return True
1470
+ if isinstance(expr, TpyName):
1471
+ if self.local_cpp_form(expr.name) is LocalCppForm.STORAGE_OPTIONAL:
1472
+ return True
1473
+ return False
1474
+
1475
+ def _is_pointer_global(self, expr: TpyExpr) -> bool:
1476
+ """Check if expression is a reference to a non-value-type global (T*)."""
1477
+ if not isinstance(expr, TpyName):
1478
+ return False
1479
+ return expr.name in self.pointer_globals and self.is_global_name(expr)
1480
+
1481
+ def is_storage_form_source(self, expr: TpyExpr) -> bool:
1482
+ """True when `expr` reads a value from a storage location.
1483
+
1484
+ Storage locations are fields, container subscripts, globals,
1485
+ locals flagged as storage-form (loop vars iterating storage
1486
+ containers, locals initialized from another storage-form source),
1487
+ and function calls whose return type is `Own[tuple[T_ref,...]]`
1488
+ (the storage form is the function's return ABI). Used by
1489
+ tuple-of-pointer-Optional callers to decide whether to emit an
1490
+ element-wise tuple_to_pointer wrap; the value's actual type /
1491
+ shape is the caller's responsibility to validate.
1492
+ """
1493
+ if isinstance(expr, (TpyFieldAccess, TpySubscript)):
1494
+ return True
1495
+ if isinstance(expr, TpyName):
1496
+ if expr.name in self.storage_form_tuple_locals:
1497
+ return True
1498
+ if self.is_global_name(expr):
1499
+ return True
1500
+ if isinstance(expr, (TpyCall, TpyMethodCall)):
1501
+ fi = expr.resolved_function_info
1502
+ if fi is not None:
1503
+ rt = unwrap_readonly(fi.return_type)
1504
+ # Two shapes both produce a storage-form tuple return:
1505
+ # * Own[tuple[T,...]] (pre-lowering form, may persist in
1506
+ # some call paths).
1507
+ # * tuple[Own[T_ref], T_value, ...] (post-own_tuple_target
1508
+ # synthesis -- the per-element Own pushes storage form
1509
+ # into each ref-typed slot).
1510
+ if isinstance(rt, OwnType) and isinstance(rt.wrapped, TupleType):
1511
+ return True
1512
+ if isinstance(rt, TupleType) and any(
1513
+ isinstance(et, OwnType) for et in rt.element_types):
1514
+ return True
1515
+ return False
1516
+
1517
+ def callee_returns_own_ptr_optional(self, init: 'TpyExpr') -> bool:
1518
+ """True when `init` is a function call returning
1519
+ `Own[OptionalType[T_ref]]` (storage form). Codegen sites bridging
1520
+ the function's `std::optional<T>` return into a pointer-form local
1521
+ check this to know they need the `optional_to_ptr` lift.
1522
+ """
1523
+ if not isinstance(init, (TpyCall, TpyMethodCall)):
1524
+ return False
1525
+ fi = init.resolved_function_info
1526
+ if fi is None:
1527
+ return False
1528
+ return is_own_pointer_repr_optional(fi.return_type)
1529
+
1530
+ def is_own_ptr_variant_param(self, name: str) -> bool:
1531
+ """True when `name` is a function parameter typed
1532
+ `Own[UnionType non-value]` (pointer-variant Union, storage form
1533
+ at the param ABI). Mirror of `optional_locals` for unions;
1534
+ consumers needing the `to_ptr_variant` lift check this.
1535
+ """
1536
+ ptype = self.current_func_params.get(name)
1537
+ if ptype is None:
1538
+ return False
1539
+ peeled = unwrap_readonly(ptype)
1540
+ return (isinstance(peeled, OwnType)
1541
+ and isinstance(peeled.wrapped, UnionType)
1542
+ and self.is_ptr_variant_union(peeled.wrapped))
1543
+
1544
+ def local_cpp_form(self, name: str) -> LocalCppForm:
1545
+ """Classify a name's C++ representation -- see `LocalCppForm`.
1546
+
1547
+ Priority order matters: `optional_locals` is a subset of
1548
+ `pointer_locals` (Own[Opt[T_ref]] params live in both for arrow
1549
+ access vs. lift dispatch), so OPTIONAL_STORAGE must take
1550
+ precedence over POINTER. STORAGE_OPTIONAL tracks iteration-source
1551
+ loop/unpack vars (populated only by
1552
+ `register_loop_var_storage_form`); its population is
1553
+ producer-disjoint from `optional_locals` today, so the priority
1554
+ slot is conventional, not load-bearing -- a same-name shadow
1555
+ between a param and a loop var would still resolve consistently
1556
+ because both forms lift the same way via `optional_to_ptr`.
1557
+ VALUE_VARIANT (Own[Union] param) is looked up via
1558
+ `current_func_params`, not a set; it precedes PTR_VARIANT
1559
+ because a single name cannot match both.
1560
+
1561
+ For arrow-vs-deref field access use `is_pointer_local(expr)`
1562
+ instead -- it covers both POINTER and OPTIONAL_STORAGE.
1563
+ """
1564
+ if name in self.optional_locals:
1565
+ return LocalCppForm.OPTIONAL_STORAGE
1566
+ if name in self.storage_form_optional_locals:
1567
+ return LocalCppForm.STORAGE_OPTIONAL
1568
+ if self.is_own_ptr_variant_param(name):
1569
+ return LocalCppForm.VALUE_VARIANT
1570
+ if name in self.ptr_variant_locals:
1571
+ return LocalCppForm.PTR_VARIANT
1572
+ if name in self.storage_form_tuple_locals:
1573
+ return LocalCppForm.STORAGE_TUPLE
1574
+ if name in self.pointer_locals:
1575
+ return LocalCppForm.POINTER
1576
+ return LocalCppForm.VALUE
1577
+
1578
+ def iteration_yields_const(self, iterable: TpyExpr) -> bool:
1579
+ """True when iterating `iterable` binds the loop var as const.
1580
+
1581
+ Detects const-source iteration patterns where the resulting tuple
1582
+ elements come out as `const optional<T>&` -- so the storage->pointer
1583
+ wrap must use `to_cpp_return_const()` to match optional_to_ptr's
1584
+ `const T*` output.
1585
+ """
1586
+ # field-of-self in a readonly method: self is const, field is const ref
1587
+ if isinstance(iterable, TpyFieldAccess) and isinstance(iterable.obj, TpyName):
1588
+ if iterable.obj.name == "self" and "self" in self.const_ref_params:
1589
+ return True
1590
+ # const-inferred param or alias of one
1591
+ if isinstance(iterable, TpyName):
1592
+ return (iterable.name in self.const_indirect_locals
1593
+ or iterable.name in self.const_ref_params)
1594
+ return False
1595
+
1596
+ def register_frame_field_shadow(self, name: str) -> bool:
1597
+ """Mark `name` as a C++-local shadow of a frame field for the
1598
+ rest of the current scope. Body-emit consults `frame_field_shadows`
1599
+ to suppress the `(*name)` peel that would otherwise apply to a
1600
+ `generator_optional_fields` member. No-op outside a resumable
1601
+ body or if `name` is already shadowed.
1602
+
1603
+ Returns True iff this call added the entry. Callers whose
1604
+ C++-local binding is block-scoped (e.g. for-loop iter var) use
1605
+ the return value to gate a paired discard at scope exit; callers
1606
+ whose binding outlives its source statement (with-as, tuple-
1607
+ unpack -- Python scoping) ignore the return and rely on
1608
+ `reset_scope` to clear the set at function exit.
1609
+ """
1610
+ if not self.in_generator_body or name in self.frame_field_shadows:
1611
+ return False
1612
+ self.frame_field_shadows.add(name)
1613
+ return True
1614
+
1615
+ def register_loop_var_storage_form(self, name: str, elem_type: 'TpyType | None',
1616
+ iterable: 'TpyExpr',
1617
+ detect_const_source: bool = True) -> None:
1618
+ """Register a loop / unpack variable into the storage-form tracking
1619
+ sets based on its sema element type.
1620
+
1621
+ Shared by the producer-side sites: `_gen_loop_body` (regular
1622
+ for-loop) and `_enter_comp_scope` (comprehension/genexpr scope). The
1623
+ sites differ
1624
+ only in whether they detect a const-bound iteration source:
1625
+ - regular for-loop + comp/genexpr: default
1626
+ `detect_const_source=True` runs `iteration_yields_const`
1627
+ against `iterable`; the const-variant set gets populated
1628
+ when the source is const-bound.
1629
+ - generator-body for-loop: pass `detect_const_source=False`
1630
+ (no const-source channel in generator bodies today; tracked
1631
+ separately).
1632
+
1633
+ Peels `ReadonlyType` from `elem_type` -- const-source iteration
1634
+ yields `ReadonlyType(OptionalType(P))` / `ReadonlyType(TupleType(...))`
1635
+ at the sema level, but the C++ shape is the same storage form;
1636
+ const-ness rides on `iteration_yields_const`.
1637
+ """
1638
+ if elem_type is None:
1639
+ return
1640
+ elem_peeled = unwrap_readonly(elem_type)
1641
+ is_const_source = (detect_const_source
1642
+ and self.iteration_yields_const(iterable))
1643
+ if (isinstance(elem_peeled, TupleType)
1644
+ and elem_peeled.has_pointer_repr_optional_element()):
1645
+ self.storage_form_tuple_locals.add(name)
1646
+ if is_const_source:
1647
+ self.const_storage_form_tuple_locals.add(name)
1648
+ if (isinstance(elem_peeled, OptionalType)
1649
+ and elem_peeled.uses_pointer_repr()):
1650
+ self.storage_form_optional_locals.add(name)
1651
+ if is_const_source:
1652
+ self.const_storage_form_optional_locals.add(name)
1653
+
1654
+ def needs_optional_to_ptr_lift(self, name: str) -> bool:
1655
+ """True when `name` is in `OPTIONAL_STORAGE` form -- an
1656
+ `Own[Opt[T_ref]]` param whose C++ shape is `std::optional<T>`
1657
+ and must be lifted via `tpy::optional_to_ptr` to feed a `T*`
1658
+ slot.
1659
+
1660
+ For call-expression sources (function returning `Own[Opt[T_ref]]`),
1661
+ use `callee_returns_own_ptr_optional` instead -- those paths
1662
+ also allocate a slot to hold the rvalue and aren't a pure lift.
1663
+ """
1664
+ return self.local_cpp_form(name) is LocalCppForm.OPTIONAL_STORAGE
1665
+
1666
+ def needs_to_ptr_variant_lift(self, name: str) -> bool:
1667
+ """True when `name` is in `VALUE_VARIANT` form -- an
1668
+ `Own[Union nonvalue]` param whose C++ shape is `std::variant<A, B>`
1669
+ and must be lifted via `tpy::to_ptr_variant` to feed a
1670
+ `std::variant<A*, B*>` slot.
1671
+ """
1672
+ return self.local_cpp_form(name) is LocalCppForm.VALUE_VARIANT
1673
+
1674
+ def polymorphic_cast_arg(self, var_name: str, var_decl: 'TpyType | None') -> str:
1675
+ """C++ expression to use as the input to dynamic_cast<Sub*>(...)
1676
+ when narrowing the polymorphic source `var_name`.
1677
+
1678
+ Polymorphic isinstance/dynamic_cast needs a pointer input; the right
1679
+ prefix depends on the variable's C++ binding shape:
1680
+ - `self` inside a generator/async body -> `&{generator_self_ref}`
1681
+ (the body sees self via `__self: T&` for async/multi-yield or
1682
+ `(*this)` for simple generators -- both yield `T*` when address-
1683
+ of'd; the `&(*this)` form is folded by the optimizer)
1684
+ - `self` in a regular method -> `this` (already a pointer)
1685
+ - pointer-globals, pointer-locals, imported pointers
1686
+ (`is_indirect_name`) -> the bare name (already a pointer)
1687
+ - declared `Optional[Polymorphic]` (pointer-repr) param -> the bare
1688
+ name (the C++ binding is `T*`)
1689
+ - bare polymorphic param (`T&`) or anything else not above
1690
+ -> `&name` (take the address)
1691
+ """
1692
+ name_expr = TpyName(var_name)
1693
+ if var_name == "self" and self.generator_self_ref is not None:
1694
+ # In simple-generator bodies generator_self_ref is `(*this)`;
1695
+ # &(*this) == this. In async/multi-yield bodies it's `__self`
1696
+ # (a T& field on the frame struct); &__self gives T*.
1697
+ if self.generator_self_ref == "(*this)":
1698
+ return "this"
1699
+ return f"&{self.generator_self_ref}"
1700
+ if var_name == "self" and self.is_indirect_name(name_expr):
1701
+ return "this"
1702
+ escaped = escape_cpp_name(var_name)
1703
+ if self.is_indirect_name(name_expr):
1704
+ return escaped
1705
+ if polymorphic_source_is_pointer(var_decl):
1706
+ return escaped
1707
+ return f"&{escaped}"
1708
+
1709
+ def deref_view_cast_arg(self, var_name: str, depth: int) -> str:
1710
+ """C++ payload pointer for a deref-view isinstance/narrowing on owning
1711
+ wrapper `var_name`: `&(<v>.__deref__()...)` peeled `depth` times. The
1712
+ wrapper's reference-returning __deref__ yields a `[const] P&`, so the
1713
+ address-of is the same `[const] P*` dynamic_cast input the bare/Ptr/
1714
+ Optional sources expose directly. Const-ness tracks the wrapper
1715
+ receiver (auto_readonly __deref__), decided by the caller."""
1716
+ name_expr = TpyName(var_name)
1717
+ escaped = escape_cpp_name(var_name)
1718
+ base = f"(*{escaped})" if self.is_indirect_name(name_expr) else escaped
1719
+ chain = ".__deref__()" * depth
1720
+ return f"&({base}{chain})"
1721
+
1722
+ def deref_dispatch_inner(self, var_decl: 'TpyType | None', depth: int) -> 'TpyType | None':
1723
+ """Peel `depth` __deref__ steps off `var_decl` to recover the
1724
+ dispatch inner (the @dynamic protocol / polymorphic class behind an
1725
+ owning wrapper), so codegen can pick dynamic_cast vs dyn_adapter_cast
1726
+ for a deref-view narrowing."""
1727
+ cur = unwrap_readonly(var_decl) if var_decl is not None else None
1728
+ type_ops = self.analyzer.type_ops
1729
+ for _ in range(depth):
1730
+ if cur is None:
1731
+ return None
1732
+ cur = type_ops.get_deref_target_type(cur)
1733
+ if cur is not None:
1734
+ cur = unwrap_readonly(cur)
1735
+ return cur
1736
+
1737
+ def deref_dispatch_source(self, var_decl: 'TpyType | None') -> 'tuple[TpyType, int] | None':
1738
+ """Peel __deref__ steps off `var_decl` until reaching a @dynamic
1739
+ dispatch inner; return (inner, depth). Mirrors sema's
1740
+ CallAnalyzer._deref_dispatch_inner so the if-init cast-and-cache can
1741
+ recover the depth a deref-view fact omits (the fact carries only the
1742
+ narrowed subclass, not the wrapper or its deref depth)."""
1743
+ from ..typesys import is_dynamic_dispatch_inner
1744
+ cur = unwrap_readonly(var_decl) if var_decl is not None else None
1745
+ type_ops = self.analyzer.type_ops
1746
+ for depth in range(1, 9):
1747
+ if cur is None:
1748
+ return None
1749
+ target = type_ops.get_deref_target_type(cur)
1750
+ if target is None:
1751
+ return None
1752
+ inner = unwrap_readonly(target)
1753
+ if (isinstance(inner, NominalType)
1754
+ and is_dynamic_dispatch_inner(inner, self.analyzer.registry)):
1755
+ return inner, depth
1756
+ cur = inner
1757
+ return None
1758
+
1759
+ def is_indirect_name(self, expr: TpyExpr) -> bool:
1760
+ """Check if expression needs indirect access (-> / deref).
1761
+
1762
+ Unifies pointer-globals (T*), pointer-locals (T*), cross-module
1763
+ imported pointer variables, and `self` (this pointer in methods)
1764
+ -- all use -> for field/method access and (*x) for value dereference.
1765
+ """
1766
+ if isinstance(expr, TpyName) and expr.name in self.comp_local_names:
1767
+ return False
1768
+ # self -> this (pointer) in instance methods, unless inside a generator
1769
+ # where generator_self_ref is already a dereferenced reference
1770
+ if (isinstance(expr, TpyName) and expr.name == "self"
1771
+ and self.in_method and "self" not in self.current_func_params
1772
+ and self.generator_self_ref is None):
1773
+ return True
1774
+ if self._is_pointer_global(expr) or self.is_pointer_local(expr):
1775
+ return True
1776
+ if isinstance(expr, TpyName):
1777
+ imp = lookup_imported(
1778
+ self.analyzer.ctx.module_attributes,
1779
+ expr.name, SymbolKind.VARIABLE)
1780
+ if imp is not None:
1781
+ source_module, original_name = imp
1782
+ module_info = self.analyzer.registry.get_module(source_module)
1783
+ if module_info and original_name in module_info.variables:
1784
+ return module_info.variables[original_name].is_pointer
1785
+ return False
1786
+
1787
+ def setup_resumable_frame_locals(self, func: 'TpyFunction') -> None:
1788
+ """Populate `pointer_locals`, `generator_optional_fields`, and
1789
+ `generator_frame_slot_locals` for a resumable-frame body
1790
+ (generator __next__ or async __poll__).
1791
+
1792
+ For each local in `func.generator_locals`:
1793
+ - value type: not tracked (frame slot is `T name;`, no peel)
1794
+ - pointer-form (pointer-repr Optional[NonValue] OR a for-loop
1795
+ iter var with a stable lvalue source): added to
1796
+ `pointer_locals` -- same dispatch as sync pointer-locals
1797
+ - other non-value: added to both `generator_optional_fields`
1798
+ and `generator_frame_slot_locals` (frame slot is
1799
+ `tpy::frame_slot<T> name;`, reads via `(*name)`,
1800
+ writes via `.emplace(value)`)
1801
+
1802
+ Caller's contract: all three sets are cleared at body entry and
1803
+ restored on exit (matches the existing `generator_*` save/restore
1804
+ dance). `generator_for_loop_info` must already be populated.
1805
+ """
1806
+ # Pointer-form iter vars are seeded up-front so the for-loop
1807
+ # emit path doesn't need to mutate `pointer_locals` mid-emission.
1808
+ # Both the legacy struct path and the resumable for-loop emit
1809
+ # (`gen_async._prescan_resumable_for_loops`) populate
1810
+ # `generator_for_loop_info`; bodies with no for-loop leave it empty
1811
+ # (the loop is then a no-op).
1812
+ for info in self.generator_for_loop_info.values():
1813
+ if info.pointer_form_loop_var is not None:
1814
+ self.pointer_locals.add(info.pointer_form_loop_var)
1815
+ self.generator_borrow_form_loop_vars.add(info.pointer_form_loop_var)
1816
+ # Tuple-unpack targets aliasing a non-value container member:
1817
+ # stored as `T*` (alias into the live element), same dispatch
1818
+ # as a pointer-form loop var.
1819
+ self.pointer_locals.update(info.pointer_form_unpack_targets)
1820
+ self.generator_borrow_form_loop_vars.update(info.pointer_form_unpack_targets)
1821
+
1822
+ if not func.generator_locals:
1823
+ return
1824
+
1825
+ for lname, ltype in func.generator_locals:
1826
+ ltype_inner = unwrap_ref_type(ltype)
1827
+ if ltype_inner.is_value_type():
1828
+ continue
1829
+ if (isinstance(ltype_inner, OptionalType)
1830
+ and ltype_inner.uses_pointer_repr()):
1831
+ self.pointer_locals.add(lname)
1832
+ continue
1833
+ if lname in self.pointer_locals:
1834
+ continue
1835
+ self.generator_optional_fields.add(lname)
1836
+ # User-local non-pointer-form non-value frame fields are backed by
1837
+ # `tpy::frame_slot<T>` (emitted by the resumable struct field-decl
1838
+ # path in `gen_async.gen_coro_struct`). Writes route through
1839
+ # `.emplace(...)`; reads use the `(*name)` access.
1840
+ self.generator_frame_slot_locals.add(lname)
1841
+
1842
+ def is_already_pointer_source(self, expr: TpyExpr) -> bool:
1843
+ """True when `expr` renders as a `T*` value with no further lifting.
1844
+
1845
+ Composes `is_indirect_name` (pointer locals/globals/self/imports --
1846
+ names whose rendering is already `T*`) with the `Ptr[T]` source
1847
+ case (any expression whose sema type is `PtrType`). Used by the
1848
+ codegen sites that lift other source shapes via `&(...)` or
1849
+ `optional_to_ptr`: when this returns True, the bare rendering is
1850
+ already in the right shape and the lift is a bug (`T**` / wrong type).
1851
+ """
1852
+ if self.is_indirect_name(expr):
1853
+ return True
1854
+ return isinstance(self.get_expr_type(expr), PtrType)
1855
+
1856
+ def get_expr_type(self, expr: TpyExpr) -> TpyType | None:
1857
+ """Get the sema-analyzed type of an expression, unwrapping ReadonlyType.
1858
+
1859
+ Codegen doesn't need ReadonlyType (C++ handles const via signatures),
1860
+ so this always strips it.
1861
+ """
1862
+ typ = self.analyzer.get_expr_type(expr)
1863
+ return unwrap_readonly(typ) if typ is not None else None
1864
+
1865
+ def pointer_value_expr(self, expr: TpyExpr, rendered: str) -> str:
1866
+ """Return expression yielding raw pointer value for pointer names.
1867
+
1868
+ Most pointer-globals are wrapped and need one dereference (`*name`) to get
1869
+ `T*`, but globals whose declared type is already pointer-like (Ptr, Ptr[readonly[T]],
1870
+ or Optional non-value) are already `T*` and must be returned as-is.
1871
+ """
1872
+ if not self._is_pointer_global(expr):
1873
+ return rendered
1874
+ expr_type = self.get_expr_type(expr)
1875
+ if isinstance(expr_type, PtrType):
1876
+ return rendered
1877
+ if isinstance(expr_type, OptionalType) and expr_type.uses_pointer_repr():
1878
+ return rendered
1879
+ return f"(*{rendered})"
1880
+
1881
+ def _call_returns_cpp_ref(self, fi: FunctionInfo | None, obj: TpyExpr | None = None) -> bool:
1882
+ """True if this function/method call returns a C++ lvalue reference (T&).
1883
+
1884
+ User-defined functions/methods with non-value, non-generic, non-owning
1885
+ return types emit T& in C++ (via to_cpp_return()). Everything else --
1886
+ native imports, @native record methods, TypeParamRef (val_or_ref_t<T>),
1887
+ Own[T], Optional[T] -- uses value semantics.
1888
+ """
1889
+ if fi is None or fi.is_native_import:
1890
+ return False
1891
+ # Record constructors return rvalue temporaries, never C++ T&. The
1892
+ # bare `fi.return_type is the record class` test below would otherwise
1893
+ # mis-classify a constructor for a non-value-type class as returning
1894
+ # by reference -- triggering `T& var = T()` indirection at the call
1895
+ # site, which fails to bind. Set in _set_record_constructor_info.
1896
+ if fi.is_constructor:
1897
+ return False
1898
+ if obj is not None:
1899
+ # Methods on @native records have unknown C++ return convention.
1900
+ raw_obj_type = self.analyzer.get_expr_type(obj)
1901
+ obj_type = unwrap_readonly(raw_obj_type) if raw_obj_type is not None else None
1902
+ rec = self.analyzer.registry.get_record_for_type(obj_type) if obj_type else None
1903
+ if rec is None and isinstance(obj, TpyName):
1904
+ rec = self.analyzer.registry.get_record(obj.name)
1905
+ if rec is not None and rec.is_native:
1906
+ return False
1907
+ rt = unwrap_ref_type(fi.return_type)
1908
+ return (rt is not None
1909
+ and not rt.is_value_type()
1910
+ and not isinstance(rt, (TypeParamRef, OwnType, OptionalType, UnionType))
1911
+ and not is_protocol_type(rt))
1912
+
1913
+ def is_rvalue_source(self, expr: TpyExpr) -> bool:
1914
+ """Check if an expression produces an rvalue (needs a stack slot).
1915
+
1916
+ Rvalues: constructor calls, Own[T] returns, literals, binop/unop results,
1917
+ field access on rvalue objects (member of temporary).
1918
+ Lvalues: variable names, field access on lvalues, subscript, function returning T&.
1919
+
1920
+ For pointer-local init: rvalue -> new slot, lvalue -> take address.
1921
+ """
1922
+ # Names are lvalues (either pointer-locals, params, or globals)
1923
+ if isinstance(expr, TpyName):
1924
+ return False
1925
+ # Field access: rvalue iff the object is rvalue (member of temporary)
1926
+ if isinstance(expr, TpyFieldAccess):
1927
+ return self.is_rvalue_source(expr.obj)
1928
+ # Subscript into containers is an lvalue (returns T&).
1929
+ # Exception: slice calls (e.g. list_stepped_slice) may return by value.
1930
+ if isinstance(expr, TpySubscript):
1931
+ if expr.slice_function_info is not None:
1932
+ return not self._call_returns_cpp_ref(expr.slice_function_info)
1933
+ return False
1934
+ # Ternary: lvalue iff both arms are lvalues (C++ ternary with two lvalue
1935
+ # arms is itself an lvalue). Uses OR semantics: rvalue if either arm is
1936
+ # rvalue, since _gen_if_expr emits arms inline with no temp materialization
1937
+ # (unlike _gen_logical_value which materializes rvalue arms into auto&& temps).
1938
+ if isinstance(expr, TpyIfExpr):
1939
+ result_type = self.analyzer.get_expr_type(expr)
1940
+ if result_type and not result_type.is_value_type():
1941
+ return (self.is_rvalue_source(expr.then_expr)
1942
+ or self.is_rvalue_source(expr.else_expr))
1943
+ # Logical and/or with operand-return semantics: rvalue temps are
1944
+ # materialized into named variables by _gen_logical_value, so the
1945
+ # result is only an rvalue when both operands are rvalues.
1946
+ if isinstance(expr, TpyBinOp) and expr.op in ("&&", "||"):
1947
+ result_type = self.analyzer.get_expr_type(expr)
1948
+ if not is_bool_type(result_type):
1949
+ return self.is_rvalue_source(expr.left) and self.is_rvalue_source(expr.right)
1950
+ # Constructor calls, literals, ops are rvalues. Listed explicitly to
1951
+ # avoid relying on the `return True` fallthrough below for the
1952
+ # container-literal family -- preserves the listing as the canonical
1953
+ # set of rvalue-yielding expression shapes.
1954
+ if isinstance(expr, (TpyIntLiteral, TpyFloatLiteral, TpyStrLiteral,
1955
+ TpyBoolLiteral, TpyNoneLiteral,
1956
+ TpyBinOp, TpyUnaryOp)):
1957
+ return True
1958
+ if isinstance(expr, _CONTAINER_LITERAL_NODES):
1959
+ return True
1960
+ if isinstance(expr, TpyMethodCall):
1961
+ return not self._call_returns_cpp_ref(expr.resolved_function_info, expr.obj)
1962
+ # Coercions: depends on inner expr
1963
+ if isinstance(expr, TpyCoerce):
1964
+ return self.is_rvalue_source(expr.expr)
1965
+ # Function calls
1966
+ if isinstance(expr, TpyCall):
1967
+ # Expression callees -> rvalue
1968
+ if not isinstance(expr.func, TpyName):
1969
+ return True
1970
+ # Record constructors -> rvalue
1971
+ if self.analyzer.registry.get_record(expr.func_name):
1972
+ return True
1973
+ # Generic type constructors -> rvalue
1974
+ if expr.call_type is not None:
1975
+ return True
1976
+ if self.analyzer.registry.get_function(expr.func_name) is not None:
1977
+ return not self._call_returns_cpp_ref(expr.resolved_function_info)
1978
+ return True # Default: treat unknown calls as rvalue
1979
+ return True # Default: rvalue
1980
+
1981
+ def is_container_literal_expr(self, expr: TpyExpr) -> bool:
1982
+ """Predicate form of `_CONTAINER_LITERAL_NODES`; see the constant
1983
+ comment for the value-emit-rvalue rationale."""
1984
+ return isinstance(expr, _CONTAINER_LITERAL_NODES)
1985
+
1986
+ def is_value_emit_rvalue(self, expr: TpyExpr) -> bool:
1987
+ """Rvalue source whose `gen_expr` yields a value (not a `T*`), so a
1988
+ pointer-form Optional sink needs to materialize a named slot before
1989
+ taking address. Container/comprehension/generator literals plus
1990
+ rvalue field accesses (`temp.field` on a moved-from object) are the
1991
+ exhaustive set under the pointer-repr-Optional outer guard at the
1992
+ two `_gen_pointer_local_init`/`_rebind` call sites.
1993
+ """
1994
+ return self.is_container_literal_expr(expr) or (
1995
+ isinstance(expr, TpyFieldAccess) and self.is_rvalue_source(expr))
1996
+
1997
+ def is_temporary_expr(self, expr: TpyExpr) -> bool:
1998
+ """Check if an expression produces a temporary (rvalue).
1999
+
2000
+ Temporaries can't bind to non-const lvalue references, so they need
2001
+ to be stored in a temp variable when passed to mutable ref params.
2002
+
2003
+ Lvalues (don't need temps): variable names, field access
2004
+ Rvalues (need temps): literals, binary/unary ops, calls, coercions, subscripts on records
2005
+ """
2006
+ # Scalar literals: 1, 3.14, "x", True, None
2007
+ if isinstance(expr, (TpyIntLiteral, TpyFloatLiteral, TpyStrLiteral, TpyBoolLiteral, TpyNoneLiteral)):
2008
+ return True
2009
+ # Container literals, comprehensions, generator expressions.
2010
+ if self.is_container_literal_expr(expr):
2011
+ return True
2012
+ # Explicit coercions: Ptr[T]->T is dereference (lvalue), others produce temporaries
2013
+ if isinstance(expr, TpyCoerce):
2014
+ # Pointer dereference is an lvalue, not a temporary
2015
+ if isinstance(expr.actual_type, PtrType):
2016
+ return False
2017
+ return True
2018
+ # Binary and unary ops always produce temporaries
2019
+ if isinstance(expr, (TpyBinOp, TpyUnaryOp)):
2020
+ return True
2021
+ if isinstance(expr, TpyMethodCall):
2022
+ return self.is_rvalue_source(expr)
2023
+ # Subscript on user records returns by value (rvalue)
2024
+ # std::vector/array operator[] returns lvalue ref, but user __getitem__ returns by value
2025
+ if isinstance(expr, TpySubscript):
2026
+ raw_ct = self.analyzer.get_expr_type(expr.obj)
2027
+ container_type = unwrap_readonly(raw_ct) if raw_ct is not None else None
2028
+ if isinstance(container_type, NominalType) and container_type.is_user_record:
2029
+ return True
2030
+ if isinstance(expr, TpyCall):
2031
+ return self.is_rvalue_source(expr)
2032
+ return False
2033
+
2034
+ def unwrap_copy(self, expr: TpyExpr) -> TpyExpr:
2035
+ """If expr is tpy.copy(x), return x; otherwise return expr as-is."""
2036
+ if isinstance(expr, TpyCoerce):
2037
+ inner = self.unwrap_copy(expr.expr)
2038
+ return inner if inner is not expr.expr else expr
2039
+ if isinstance(expr, TpyCall) and len(expr.args) == 1 and isinstance(expr.func, TpyName):
2040
+ if expr.func_name in self.analyzer.imported_names:
2041
+ mod, fn = self.analyzer.imported_names[expr.func_name]
2042
+ if mod == "tpy" and fn == "copy":
2043
+ return expr.args[0]
2044
+ return expr
2045
+
2046
+ def contains_protocol_type(self, typ: TpyType) -> bool:
2047
+ """Check if a type contains a protocol or SelfType anywhere in its structure.
2048
+
2049
+ Used to determine if a variable should use 'auto' in C++ codegen because
2050
+ the actual type depends on template parameters.
2051
+ """
2052
+ if isinstance(typ, SelfType):
2053
+ return True
2054
+ if is_protocol_type(typ):
2055
+ return True
2056
+ elif isinstance(typ, OwnType):
2057
+ return self.contains_protocol_type(typ.wrapped)
2058
+ elif isinstance(typ, ReadonlyType):
2059
+ return self.contains_protocol_type(typ.wrapped)
2060
+ elif isinstance(typ, PtrType):
2061
+ return self.contains_protocol_type(typ.pointee)
2062
+ elif (elem_type := typ.get_element_type()) is not None:
2063
+ return self.contains_protocol_type(elem_type)
2064
+ return False