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,1997 @@
1
+ """
2
+ TurboPython Match/Case Code Generation
3
+
4
+ Generates C++ code from TurboPython match/case statements.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from collections import defaultdict
9
+ from typing import TextIO, TYPE_CHECKING
10
+
11
+ from ..typesys import (
12
+ TpyType, NominalType, NoneType, OptionalType,
13
+ PendingStrType, UnionType, RecursiveAliasInstanceType,
14
+ LiteralType,
15
+ unwrap_readonly, is_any_str_type,
16
+ )
17
+ from .variant_access import VariantAccess
18
+ from ..parse import (
19
+ TpyStmt, TpyExpr, TpyFieldAccess, TpyName, TpyMatch, TpyMatchCase, TpyPattern,
20
+ TpySubscript, TpyWildcardPattern, TpyCapturePattern, TpyClassPattern,
21
+ TpyLiteralPattern, TpyValuePattern, TpyOrPattern, TpyAsPattern,
22
+ )
23
+
24
+
25
+ def _match_subject_is_lvalue(expr: TpyExpr) -> bool:
26
+ """A match subject is an lvalue when binding it with `auto&` is
27
+ safe (won't dangle) and useful (lets `case C() as v: v.f = ...`
28
+ write through to the original storage).
29
+
30
+ Plain names, field accesses whose target is itself an lvalue,
31
+ and subscripts on lvalue targets all qualify. Calls, literals,
32
+ and constructed temporaries do not."""
33
+ if isinstance(expr, TpyName):
34
+ return True
35
+ if isinstance(expr, TpyFieldAccess):
36
+ return _match_subject_is_lvalue(expr.obj)
37
+ if isinstance(expr, TpySubscript):
38
+ return _match_subject_is_lvalue(expr.obj)
39
+ return False
40
+ from .context import INDENT, CodeGenError, escape_cpp_name, escape_cpp_string, escape_cpp_char, cpp_string_literal_expr
41
+ from .string_dispatch import find_best_discriminator, STRING_SWITCH_THRESHOLD
42
+ from ..type_def_registry import is_fixed_int_type, is_bool_type, is_enum_type
43
+ from ..liveness import stmts_terminate
44
+
45
+ if TYPE_CHECKING:
46
+ from ..parse import SourceLocation
47
+ from .context import CodeGenContext
48
+ from .types import TypeResolver
49
+ from .expressions import ExpressionGenerator
50
+ from .statements import StatementGenerator
51
+
52
+
53
+ class MatchGenerator:
54
+ """Generates C++ code from TurboPython match/case statements."""
55
+
56
+ def __init__(self, ctx: CodeGenContext, types: TypeResolver,
57
+ expressions: ExpressionGenerator, stmts: StatementGenerator):
58
+ self.ctx = ctx
59
+ self.types = types
60
+ self.expressions = expressions
61
+ self.stmts = stmts
62
+
63
+ def gen_match(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
64
+ """Generate a match/case statement. Uses switch when possible, if/elif otherwise."""
65
+ assert stmt.subject_type is not None
66
+ subject_type = unwrap_readonly(stmt.subject_type)
67
+
68
+ # @overload dead branch elimination for match on union subject
69
+ if (isinstance(subject_type, UnionType)
70
+ and isinstance(stmt.subject, TpyName)
71
+ and self.ctx.overload_param_types):
72
+ concrete = self.ctx.overload_param_types.get(stmt.subject.name)
73
+ if concrete is not None:
74
+ self._gen_match_overload_specialized(out, stmt, concrete, indent)
75
+ return
76
+
77
+ # Pre-declare variables first declared inside match arms
78
+ self.stmts._emit_branch_decls(out, stmt, indent)
79
+
80
+ # Evaluate subject and bind to a local
81
+ subject_code = self.expressions.gen_expr(stmt.subject)
82
+ self.ctx.temps.flush(out, indent)
83
+ # Use auto& for lvalue subjects (safe reference; lets a
84
+ # mutating `case C() as v: v.field = ...` arm write through
85
+ # to the original storage). Plain names are lvalues; so is a
86
+ # field access whose target is itself an lvalue (e.g.
87
+ # `self.payload`). Everything else (calls, temporaries) is
88
+ # copied as `auto` to avoid dangling references.
89
+ subject_is_lvalue = _match_subject_is_lvalue(stmt.subject)
90
+ binding = "auto&" if subject_is_lvalue else "auto"
91
+ # Record whether the subject is a stable frame-resident lvalue, for
92
+ # the resumable pointer-form binding emit: a pointer-into-subject arm
93
+ # binding survives a suspension only if the subject's storage outlives
94
+ # it. A non-lvalue subject is a dispatch-local copy; a *narrowed* name
95
+ # (union/isinstance) renders to a dispatch-local extraction alias
96
+ # (`__case_N`) even though it is syntactically a name -- so a nested
97
+ # `match` on a narrowed subject is NOT frame-stable. Both are unsafe
98
+ # for a pointer-form binding (the emit rejects that combination).
99
+ # Set unconditionally; read only on the resumable pointer-form path.
100
+ # Re-set per match (incl. nested) -- arm bindings emit before any
101
+ # nested-match arm body runs, so no save/restore is needed.
102
+ subject_is_narrowed = (isinstance(stmt.subject, TpyName)
103
+ and stmt.subject.name in self.ctx.narrowed_vars)
104
+ self.ctx.resumable_match_subject_is_lvalue = (
105
+ subject_is_lvalue and not subject_is_narrowed)
106
+ self.ctx.resumable_match_loc = stmt.loc
107
+ out.write(f"{indent}{binding} __match_subject = {subject_code};\n")
108
+
109
+ if isinstance(subject_type, (UnionType, RecursiveAliasInstanceType)):
110
+ # Generic recursive alias instances dispatch through the same
111
+ # variant-index path; _variant_index / VariantAccess read members
112
+ # via wrapper_info() / needs_wrapper(), both of which the instance
113
+ # implements.
114
+ has_guard = any(c.guard is not None for c in stmt.cases)
115
+ # Also use guarded path when union field guards cause multiple
116
+ # arms to share the same variant index
117
+ if not has_guard:
118
+ has_guard = self._has_shared_variant_index(stmt, subject_type)
119
+ if has_guard:
120
+ self._gen_match_guarded_union(out, stmt, subject_type, indent)
121
+ else:
122
+ self._gen_match_switch_union(out, stmt, subject_type, indent)
123
+ elif is_enum_type(subject_type):
124
+ self._gen_match_switch_enum(out, stmt, indent)
125
+ elif isinstance(subject_type, LiteralType):
126
+ base = subject_type.base_type
127
+ if is_any_str_type(base):
128
+ if self._should_switch_str(stmt):
129
+ self._gen_match_switch_str(out, stmt, indent)
130
+ else:
131
+ self._gen_match_if_elif(out, stmt, indent)
132
+ elif is_fixed_int_type(base):
133
+ self._gen_match_switch_primitive(out, stmt, indent)
134
+ else:
135
+ # bool (and anything else literal-like) falls through to if/elif:
136
+ # `switch(bool_var)` is valid C++ but trips -Wswitch-bool, and an
137
+ # if-chain is the same shape with two arms anyway.
138
+ self._gen_match_if_elif(out, stmt, indent)
139
+ elif is_fixed_int_type(subject_type):
140
+ self._gen_match_switch_primitive(out, stmt, indent)
141
+ elif is_bool_type(subject_type):
142
+ self._gen_match_if_elif(out, stmt, indent)
143
+ elif isinstance(subject_type, NominalType) and subject_type.is_user_record:
144
+ has_guard = any(c.guard is not None for c in stmt.cases)
145
+ if has_guard:
146
+ self._gen_match_guarded_record(out, stmt, indent)
147
+ else:
148
+ self._gen_match_if_elif_record(out, stmt, indent)
149
+ elif isinstance(subject_type, OptionalType):
150
+ partition = self._partition_optional_cases(stmt.cases)
151
+ if partition is not None:
152
+ none_cases, inner_cases = partition
153
+ self._gen_match_optimized_optional(
154
+ out, stmt, subject_type, none_cases, inner_cases, indent,
155
+ )
156
+ else:
157
+ self._gen_match_if_elif_optional(out, stmt, subject_type, indent)
158
+ elif is_any_str_type(subject_type):
159
+ if self._should_switch_str(stmt):
160
+ self._gen_match_switch_str(out, stmt, indent)
161
+ else:
162
+ self._gen_match_if_elif(out, stmt, indent)
163
+ else:
164
+ self._gen_match_if_elif(out, stmt, indent)
165
+
166
+ # If the match is exhaustive (sema-proven) AND every arm body
167
+ # terminates, the post-match control point is unreachable. Tell the
168
+ # compiler so -- otherwise it warns "control reaches end of non-void
169
+ # function" when the match is the function's last statement.
170
+ #
171
+ # For non-exhaustive matches, falling through the end-label is the
172
+ # user's intent (sema only warns), so emitting std::unreachable()
173
+ # there would let the optimizer eliminate code that the user expects
174
+ # to execute.
175
+ if (stmt.is_exhaustive
176
+ and stmt.cases
177
+ and all(stmts_terminate(c.body) for c in stmt.cases)):
178
+ out.write(f"{indent}::std::unreachable();\n")
179
+
180
+ def _gen_match_overload_specialized(
181
+ self, out: TextIO, stmt: TpyMatch, concrete_type: 'TpyType', indent: str,
182
+ ) -> None:
183
+ """Emit only the matching arm for a match on a concrete overload param.
184
+
185
+ The subject variable has a concrete (non-variant) type in this overload,
186
+ so we find the arm whose class pattern matches and emit its body directly.
187
+ """
188
+ assert isinstance(stmt.subject, TpyName)
189
+ subject_name = stmt.subject.name
190
+
191
+ # This @overload-specialization path emits the matched arm body
192
+ # directly via `gen_stmt`, bypassing `_emit_case_body` -- so the
193
+ # resumable-frame arm-routing hook would never fire and a suspension
194
+ # in the arm would be emitted as straight-line code (dropped). Refuse
195
+ # rather than silently miscompile; the resumable `match` decomposition
196
+ # does not cover the overload-specialized subject.
197
+ if self.ctx.resumable_arm_emitter is not None:
198
+ raise CodeGenError(
199
+ "a `yield`/`await` inside a `match` on an @overload-"
200
+ "specialized parameter is not yet supported",
201
+ loc=stmt.loc,
202
+ )
203
+
204
+ def emit_field_bindings(pattern: TpyClassPattern) -> None:
205
+ self._gen_match_field_bindings(out, pattern, subject_name, indent)
206
+
207
+ for case in stmt.cases:
208
+ pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
209
+
210
+ if isinstance(pattern, TpyClassPattern) and pattern.resolved_type is not None:
211
+ if pattern.resolved_type == concrete_type:
212
+ emit_field_bindings(pattern)
213
+ if as_name:
214
+ out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
215
+ for s in case.body:
216
+ self.stmts.gen_stmt(out, s)
217
+ return
218
+
219
+ elif isinstance(pattern, TpyOrPattern):
220
+ for alt in pattern.patterns:
221
+ if isinstance(alt, TpyClassPattern) and alt.resolved_type == concrete_type:
222
+ emit_field_bindings(alt)
223
+ if as_name:
224
+ out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
225
+ for s in case.body:
226
+ self.stmts.gen_stmt(out, s)
227
+ return
228
+
229
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
230
+ if isinstance(pattern, TpyCapturePattern):
231
+ out.write(f"{indent}auto& {escape_cpp_name(pattern.name)} = {subject_name};\n")
232
+ if as_name:
233
+ out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
234
+ for s in case.body:
235
+ self.stmts.gen_stmt(out, s)
236
+ return
237
+
238
+ raise AssertionError(
239
+ f"No match arm found for concrete type {concrete_type} "
240
+ f"in @overload specialization"
241
+ )
242
+
243
+ def _subject_is_ptr_variant(
244
+ self, subject: TpyExpr, subject_type: TpyType,
245
+ ) -> bool:
246
+ """Whether the match subject's storage is pointer-variant.
247
+
248
+ ``is_ptr_variant_union(subject_type)`` reflects the type's repr in
249
+ its primary contexts (params, locals, returns) but the same union
250
+ is stored value-variant when it's a record field or container
251
+ element. The match ``get_expr`` ('*std::get' vs 'std::get') has to
252
+ match actual storage, not just type. Falls back to the assignment
253
+ path's classifier so the same rules apply on both sides.
254
+ """
255
+ if not self.ctx.is_ptr_variant_union(subject_type):
256
+ return False
257
+ return self.ctx.is_ptr_variant_source(subject)
258
+
259
+ def _gen_match_switch_union(
260
+ self, out: TextIO, stmt: TpyMatch, subject_type: UnionType, indent: str,
261
+ ) -> None:
262
+ """Generate switch (__match_subject.index()) for union subjects."""
263
+ inner = INDENT * (self.ctx.indent_level + 1)
264
+ is_ptr_var = self._subject_is_ptr_variant(stmt.subject, subject_type)
265
+ va = VariantAccess("__match_subject", subject_type, is_ptr_variant=is_ptr_var)
266
+ out.write(f"{indent}switch ({va.index_expr()}) {{\n")
267
+
268
+ for i, case in enumerate(stmt.cases):
269
+ self.ctx.emit_source_comment(out, case.loc, indent)
270
+ pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
271
+
272
+ if isinstance(pattern, TpyClassPattern):
273
+ assert pattern.resolved_type is not None
274
+ idx = self._variant_index(subject_type, pattern.resolved_type)
275
+ out.write(f"{indent}case {idx}: {{\n")
276
+ case_var: str | None = None
277
+ if pattern.keywords or case.type_facts:
278
+ case_var = f"__case_{i}"
279
+ out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
280
+ if pattern.keywords:
281
+ self._gen_match_field_bindings(out, pattern, case_var, inner)
282
+ self._emit_binding(out, as_name, as_raw_name,
283
+ case_var or va.get_by_index(idx), inner)
284
+ # Narrowing
285
+ saved_narrow: dict[str, str | None] = {}
286
+ if case.type_facts:
287
+ for var_name in case.type_facts:
288
+ saved_narrow[var_name] = self.ctx.narrowed_vars.get(var_name)
289
+ self.ctx.narrowed_vars[var_name] = case_var
290
+ self.ctx.indent_level += 1
291
+ self._emit_case_body(out, case.body, case.type_facts)
292
+ self.ctx.indent_level -= 1
293
+ self.stmts.ctx.restore_narrowed_vars(saved_narrow)
294
+ out.write(f"{inner}break;\n")
295
+ out.write(f"{indent}}}\n")
296
+
297
+ elif isinstance(pattern, TpyOrPattern):
298
+ has_bindings = any(
299
+ isinstance(alt, TpyClassPattern) and alt.keywords
300
+ for alt in pattern.patterns
301
+ )
302
+ has_wildcard = any(
303
+ isinstance(alt, (TpyWildcardPattern, TpyCapturePattern))
304
+ for alt in pattern.patterns
305
+ )
306
+ if has_wildcard:
307
+ # Wildcard subsumes all alternatives -> default
308
+ out.write(f"{indent}default: {{\n")
309
+ self.ctx.indent_level += 1
310
+ self._emit_case_body(out, case.body, case.type_facts)
311
+ self.ctx.indent_level -= 1
312
+ out.write(f"{inner}break;\n")
313
+ out.write(f"{indent}}}\n")
314
+ elif not has_bindings:
315
+ # No bindings: case fallthrough
316
+ for alt in pattern.patterns:
317
+ assert isinstance(alt, TpyClassPattern) and alt.resolved_type is not None
318
+ idx = self._variant_index(subject_type, alt.resolved_type)
319
+ out.write(f"{indent}case {idx}:\n")
320
+ out.write(f"{indent}{{\n")
321
+ self.ctx.indent_level += 1
322
+ self._emit_case_body(out, case.body, case.type_facts)
323
+ self.ctx.indent_level -= 1
324
+ out.write(f"{inner}break;\n")
325
+ out.write(f"{indent}}}\n")
326
+ else:
327
+ # With bindings: body duplication per alternative
328
+ for j, alt in enumerate(pattern.patterns):
329
+ assert isinstance(alt, TpyClassPattern) and alt.resolved_type is not None
330
+ idx = self._variant_index(subject_type, alt.resolved_type)
331
+ out.write(f"{indent}case {idx}: {{\n")
332
+ case_var = f"__case_{i}_{j}"
333
+ out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
334
+ if alt.keywords:
335
+ self._gen_match_field_bindings(out, alt, case_var, inner)
336
+ saved = self._apply_narrowing(case.type_facts, case_var)
337
+ self.ctx.indent_level += 1
338
+ self._emit_case_body(out, case.body, case.type_facts)
339
+ self.ctx.indent_level -= 1
340
+ self.stmts.ctx.restore_narrowed_vars(saved)
341
+ out.write(f"{inner}break;\n")
342
+ out.write(f"{indent}}}\n")
343
+
344
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
345
+ self._gen_switch_default_arm(
346
+ out, pattern, as_name, as_raw_name, case.body,
347
+ indent, inner, case.type_facts)
348
+
349
+ elif isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
350
+ idx = self._variant_index(subject_type, NoneType())
351
+ out.write(f"{indent}case {idx}: {{\n")
352
+ self.ctx.indent_level += 1
353
+ self._emit_case_body(out, case.body, case.type_facts)
354
+ self.ctx.indent_level -= 1
355
+ out.write(f"{inner}break;\n")
356
+ out.write(f"{indent}}}\n")
357
+
358
+ else:
359
+ raise CodeGenError(f"Unsupported pattern in union switch: {type(pattern).__name__}")
360
+
361
+ out.write(f"{indent}}}\n")
362
+
363
+ def _gen_match_switch_enum(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
364
+ """Generate switch (__match_subject) for enum subjects."""
365
+ groups = self._group_switch_arms(stmt, kind="enum")
366
+ self._emit_switch_groups(out, groups, indent, is_exhaustive=stmt.is_exhaustive)
367
+
368
+ def _gen_match_switch_primitive(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
369
+ """Generate switch (__match_subject) for int/bool subjects."""
370
+ groups = self._group_switch_arms(stmt, kind="primitive")
371
+ self._emit_switch_groups(out, groups, indent, is_exhaustive=stmt.is_exhaustive)
372
+
373
+ # Entry in a switch arm group:
374
+ # (guard, body, capture_escaped, as_escaped, raw_names, loc, type_facts)
375
+ # raw_names: set of raw Python names for declared_vars lookup
376
+ _SwitchEntry = tuple[
377
+ TpyExpr | None, list['TpyStmt'], str | None, str | None, set[str],
378
+ 'SourceLocation | None', dict[str, TpyType],
379
+ ]
380
+
381
+ def _group_switch_arms(
382
+ self, stmt_or_cases: 'TpyMatch | list[TpyMatchCase]', kind: str,
383
+ ) -> list[tuple[list[str], list[_SwitchEntry]]]:
384
+ """Group match cases by switch label for enum/primitive subjects.
385
+
386
+ Returns a list of (labels, entries) where:
387
+ - labels: list of case label strings, or ["default"] for wildcard
388
+ - entries: list of (guard, body, cap_escaped, as_escaped, raw_names, loc)
389
+ Same-value cases with guards are merged into a single group.
390
+ Entries are ordered guarded-first, unguarded-last (enforced by sema
391
+ duplicate-case check which only allows same-value repeats with guards).
392
+ """
393
+ cases: list[TpyMatchCase] = (
394
+ stmt_or_cases if isinstance(stmt_or_cases, list) else stmt_or_cases.cases
395
+ )
396
+ groups: dict[str, tuple[list[str], list[MatchGenerator._SwitchEntry]]] = {}
397
+ default_entries: list[MatchGenerator._SwitchEntry] = []
398
+
399
+ for case in cases:
400
+ pattern, as_escaped, as_raw = self._unwrap_as_pattern(case.pattern)
401
+ raw_names: set[str] = set()
402
+ if as_raw is not None:
403
+ raw_names.add(as_raw)
404
+
405
+ if isinstance(pattern, (TpyValuePattern, TpyLiteralPattern)):
406
+ if kind == "enum":
407
+ assert isinstance(pattern, TpyValuePattern)
408
+ label = self.expressions.gen_expr(pattern.expr)
409
+ else:
410
+ assert isinstance(pattern, TpyLiteralPattern)
411
+ label = self._switch_literal_label(pattern)
412
+ entry: MatchGenerator._SwitchEntry = (
413
+ case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts,
414
+ )
415
+ if label in groups:
416
+ groups[label][1].append(entry)
417
+ else:
418
+ groups[label] = ([label], [entry])
419
+
420
+ elif isinstance(pattern, TpyOrPattern):
421
+ has_wild = any(
422
+ isinstance(alt, (TpyWildcardPattern, TpyCapturePattern))
423
+ for alt in pattern.patterns
424
+ )
425
+ if has_wild:
426
+ default_entries.append((case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts))
427
+ else:
428
+ labels = []
429
+ for alt in pattern.patterns:
430
+ if kind == "enum":
431
+ assert isinstance(alt, TpyValuePattern)
432
+ labels.append(self.expressions.gen_expr(alt.expr))
433
+ else:
434
+ assert isinstance(alt, TpyLiteralPattern)
435
+ labels.append(self._switch_literal_label(alt))
436
+ key = "|".join(labels)
437
+ entry = (case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts)
438
+ if key in groups:
439
+ groups[key][1].append(entry)
440
+ else:
441
+ groups[key] = (labels, [entry])
442
+
443
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
444
+ cap_escaped = escape_cpp_name(pattern.name) if isinstance(pattern, TpyCapturePattern) else None
445
+ if isinstance(pattern, TpyCapturePattern):
446
+ raw_names.add(pattern.name)
447
+ default_entries.append((case.guard, case.body, cap_escaped, as_escaped, raw_names, case.loc, case.type_facts))
448
+
449
+ else:
450
+ raise CodeGenError(f"Unsupported pattern in {kind} switch: {type(pattern).__name__}")
451
+
452
+ result = list(groups.values())
453
+ if default_entries:
454
+ result.append((["default"], default_entries))
455
+ return result
456
+
457
+ def _emit_switch_groups(
458
+ self, out: TextIO,
459
+ groups: list[tuple[list[str], list[_SwitchEntry]]],
460
+ indent: str,
461
+ subject_expr: str = "__match_subject",
462
+ is_exhaustive: bool = False,
463
+ ) -> None:
464
+ """Emit a switch statement from grouped arms.
465
+
466
+ is_exhaustive: caller's promise that the match covers every possible
467
+ subject value. When true, the synthetic ``default: break;`` is omitted
468
+ so future enum members added upstream still trigger ``-Wswitch``.
469
+ """
470
+ inner = INDENT * (self.ctx.indent_level + 1)
471
+
472
+ # Check if any non-default group needs guard fallback to default
473
+ has_default = any(labels == ["default"] for labels, _ in groups)
474
+ needs_default_goto = has_default and any(
475
+ labels != ["default"]
476
+ and all(g is not None for g, _, _, _, _, _, _ in entries)
477
+ for labels, entries in groups
478
+ )
479
+ default_label: str | None = None
480
+ if needs_default_goto:
481
+ self.ctx.match_counter += 1
482
+ default_label = f"__match_default_{self.ctx.match_counter}"
483
+
484
+ out.write(f"{indent}switch ({subject_expr}) {{\n")
485
+
486
+ for labels, entries in groups:
487
+ # Emit source comment for first entry in group
488
+ if entries:
489
+ self.ctx.emit_source_comment(out, entries[0][5], indent)
490
+ # Emit case labels
491
+ if labels == ["default"]:
492
+ if default_label is not None:
493
+ out.write(f"{indent}default: {default_label}: {{\n")
494
+ else:
495
+ out.write(f"{indent}default: {{\n")
496
+ elif len(labels) == 1:
497
+ out.write(f"{indent}case {labels[0]}: {{\n")
498
+ else:
499
+ for label in labels:
500
+ out.write(f"{indent}case {label}:\n")
501
+ out.write(f"{indent}{{\n")
502
+
503
+ # Bound literal_facts/protocol_narrowings to the case scope.
504
+ # type_facts from the first entry apply to the whole group (all
505
+ # entries in a group match the same value, so facts are equivalent).
506
+ type_facts_0 = entries[0][6]
507
+
508
+ # Single entry, no guard -> simple body
509
+ if len(entries) == 1 and entries[0][0] is None:
510
+ _, body, cap, as_name, raw_names, _loc, _tf = entries[0]
511
+ self._emit_switch_binding(out, cap, as_name, raw_names, inner, subject_expr)
512
+ self.ctx.indent_level += 1
513
+ self._emit_case_body(out, body, type_facts_0)
514
+ self.ctx.indent_level -= 1
515
+ else:
516
+ # Emit capture/as binding before the guard chain so guards
517
+ # can reference the bound variable
518
+ bindings_emitted: set[str] = set()
519
+ for _g, _b, cap, as_name, raw_names, _loc, _tf in entries:
520
+ for escaped, raw in self._binding_pairs(cap, as_name, raw_names):
521
+ if escaped not in bindings_emitted:
522
+ self._emit_binding(out, escaped, raw, subject_expr, inner)
523
+ bindings_emitted.add(escaped)
524
+
525
+ # Guard chain: if (g1) { body1 } else if (g2) { body2 } else { fallback }
526
+ has_unguarded = any(g is None for g, _, _, _, _, _, _ in entries)
527
+ if_opened = False
528
+ for _j, (guard, body, _cap, _as, _raw, _loc, _tf) in enumerate(entries):
529
+ if guard is not None:
530
+ guard_code = self.expressions.gen_expr(guard)
531
+ self.ctx.temps.flush(out, inner)
532
+ keyword = "if" if not if_opened else "} else if"
533
+ if_opened = True
534
+ out.write(f"{inner}{keyword} ({guard_code}) {{\n")
535
+ self.ctx.indent_level += 2
536
+ self._emit_case_body(out, body, type_facts_0)
537
+ self.ctx.indent_level -= 2
538
+ else:
539
+ # Unguarded entry: final else
540
+ out.write(f"{inner}}} else {{\n")
541
+ self.ctx.indent_level += 2
542
+ self._emit_case_body(out, body, type_facts_0)
543
+ self.ctx.indent_level -= 2
544
+ # Close last if/else and add goto fallback if needed
545
+ if has_unguarded:
546
+ out.write(f"{inner}}}\n")
547
+ elif default_label is not None and labels != ["default"]:
548
+ out.write(f"{inner}}}\n")
549
+ out.write(f"{inner}goto {default_label};\n")
550
+ else:
551
+ out.write(f"{inner}}}\n")
552
+
553
+ out.write(f"{inner}break;\n")
554
+ out.write(f"{indent}}}\n")
555
+
556
+ # Synthetic default for non-exhaustive switches. -Wswitch (in -Wall)
557
+ # flags an enum switch that doesn't list every enum value; non-
558
+ # exhaustive matches on int/bool don't trigger it but adding a
559
+ # default is harmless. Skipped when the user already provided a
560
+ # wildcard arm (`has_default`) or when sema proved the match
561
+ # exhaustive (so future enum members added upstream still trigger
562
+ # -Wswitch instead of being silently swallowed).
563
+ if not has_default and not is_exhaustive:
564
+ out.write(f"{indent}default: break;\n")
565
+
566
+ out.write(f"{indent}}}\n")
567
+
568
+ def _emit_binding(
569
+ self, out: TextIO, escaped: str | None, raw: str | None,
570
+ subject_expr: str, indent: str,
571
+ ) -> None:
572
+ """Emit a variable binding if name is not None.
573
+
574
+ Uses assignment if pre-declared (leaked from match arm), auto& otherwise.
575
+ """
576
+ if escaped is None:
577
+ return
578
+ raw_name = raw or escaped
579
+ # Resumable frame (H1): a bound name that is a frame field must be
580
+ # WRITTEN to the field, not re-declared as a shadowing C++ local --
581
+ # in a decomposed (suspending) match the arm body is a separate
582
+ # state that reads the live field, so an `auto&` dispatch-local
583
+ # would be lost at the state split. The value/frame_slot/pointer
584
+ # split mirrors the field-write logic in `_gen_assign`.
585
+ if (self.ctx.in_generator_body
586
+ and raw_name in self.ctx.generator_field_names):
587
+ if raw_name in self.ctx.pointer_locals:
588
+ # Pointer-repr `Optional` field: bridge storage form
589
+ # (`std::optional<T>`) to the borrow-form `T*` frame slot via
590
+ # `optional_to_ptr` (raw `&` would take the address of the
591
+ # optional -- wrong type). The pointer aliases into the
592
+ # subject, so a non-lvalue subject (dispatch-local copy) would
593
+ # dangle across the suspension: reject rather than miscompile.
594
+ if not self.ctx.resumable_match_subject_is_lvalue:
595
+ raise CodeGenError(
596
+ "binding a `T | None` field from a non-lvalue "
597
+ "`match` subject inside a generator/`async` is not "
598
+ "yet supported (the binding would dangle across a "
599
+ "suspension); bind the subject to a local first",
600
+ loc=self.ctx.resumable_match_loc)
601
+ out.write(
602
+ f"{indent}{escaped} = "
603
+ f"::tpy::optional_to_ptr({subject_expr});\n")
604
+ elif raw_name in self.ctx.generator_frame_slot_locals:
605
+ out.write(f"{indent}{escaped}.emplace({subject_expr});\n")
606
+ else:
607
+ out.write(f"{indent}{escaped} = {subject_expr};\n")
608
+ return
609
+ if raw_name in self.ctx.declared_vars:
610
+ out.write(f"{indent}{escaped} = {subject_expr};\n")
611
+ else:
612
+ out.write(f"{indent}auto& {escaped} = {subject_expr};\n")
613
+
614
+ def _emit_switch_binding(
615
+ self, out: TextIO, cap: str | None, as_name: str | None,
616
+ raw_names: set[str], inner: str,
617
+ subject_expr: str = "__match_subject",
618
+ ) -> None:
619
+ """Emit capture/as binding in a switch arm, respecting declared_vars."""
620
+ for escaped, raw in self._binding_pairs(cap, as_name, raw_names):
621
+ self._emit_binding(out, escaped, raw, subject_expr, inner)
622
+
623
+ @staticmethod
624
+ def _binding_pairs(
625
+ cap: str | None, as_name: str | None, raw_names: set[str],
626
+ ) -> list[tuple[str, str]]:
627
+ """Return (escaped_name, raw_name) pairs for binding emission."""
628
+ pairs: list[tuple[str, str]] = []
629
+ # raw_names may contain 1 or 2 entries; match escaped names to raw
630
+ raw_list = list(raw_names)
631
+ if cap is not None:
632
+ raw = next((r for r in raw_list if escape_cpp_name(r) == cap), cap)
633
+ pairs.append((cap, raw))
634
+ if as_name is not None and as_name != cap:
635
+ raw = next((r for r in raw_list if escape_cpp_name(r) == as_name), as_name)
636
+ pairs.append((as_name, raw))
637
+ return pairs
638
+
639
+ def _unwrap_as_pattern(
640
+ self, pattern: TpyPattern,
641
+ ) -> tuple[TpyPattern, str | None, str | None]:
642
+ """Unwrap as-pattern, returning (inner_pattern, escaped_as_name, raw_as_name)."""
643
+ if isinstance(pattern, TpyAsPattern):
644
+ return pattern.pattern, escape_cpp_name(pattern.name), pattern.name
645
+ return pattern, None, None
646
+
647
+ def _gen_switch_default_arm(
648
+ self, out: TextIO, pattern: TpyWildcardPattern | TpyCapturePattern,
649
+ as_name: str | None, as_raw_name: str | None,
650
+ body: list[TpyStmt], indent: str, inner: str,
651
+ type_facts: dict[str, TpyType] | None = None,
652
+ ) -> None:
653
+ """Emit a default: arm in a switch statement."""
654
+ out.write(f"{indent}default: {{\n")
655
+ if isinstance(pattern, TpyCapturePattern):
656
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
657
+ self._emit_binding(out, as_name, as_raw_name, "__match_subject", inner)
658
+ self.ctx.indent_level += 1
659
+ self._emit_case_body(out, body, type_facts)
660
+ self.ctx.indent_level -= 1
661
+ out.write(f"{inner}break;\n")
662
+ out.write(f"{indent}}}\n")
663
+
664
+ def _gen_match_guarded_union(
665
+ self, out: TextIO, stmt: TpyMatch, subject_type: UnionType, indent: str,
666
+ ) -> None:
667
+ """Generate guarded match on union using switch(index()) with guards inside case blocks.
668
+
669
+ Groups arms by variant type so each type is checked exactly once via
670
+ switch, instead of duplicating holds_alternative per arm.
671
+ """
672
+ self.ctx.match_counter += 1
673
+ end_label = f"__match_end_{self.ctx.match_counter}"
674
+ inner = INDENT * (self.ctx.indent_level + 1)
675
+ inner2 = INDENT * (self.ctx.indent_level + 2)
676
+ is_ptr_var = self._subject_is_ptr_variant(stmt.subject, subject_type)
677
+ va = VariantAccess("__match_subject", subject_type, is_ptr_variant=is_ptr_var)
678
+ # Variant indices come from the wrapper struct's std::variant ordering;
679
+ # for narrowed recursive unions this is wider than subject_type.members.
680
+ wrapper = subject_type.wrapper_info()
681
+ full_members = wrapper.full_members if wrapper is not None else subject_type.members
682
+
683
+ # Collect arms per variant type index.
684
+ # Each entry: (case, pattern_for_this_type, as_name, as_raw_name)
685
+ type_arms: dict[int, list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]]] = {
686
+ i: [] for i in range(len(full_members))
687
+ }
688
+
689
+ for case in stmt.cases:
690
+ pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
691
+
692
+ if isinstance(pattern, TpyClassPattern):
693
+ assert pattern.resolved_type is not None
694
+ idx = self._variant_index(subject_type, pattern.resolved_type)
695
+ type_arms[idx].append((case, pattern, as_name, as_raw_name))
696
+
697
+ elif isinstance(pattern, TpyOrPattern):
698
+ # Track which indices got a class alt from this or-pattern
699
+ # so wildcard alts don't duplicate into the same index
700
+ or_covered: set[int] = set()
701
+ for alt in pattern.patterns:
702
+ if isinstance(alt, TpyClassPattern):
703
+ assert alt.resolved_type is not None
704
+ idx = self._variant_index(subject_type, alt.resolved_type)
705
+ type_arms[idx].append((case, alt, as_name, as_raw_name))
706
+ or_covered.add(idx)
707
+ elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
708
+ for idx in type_arms:
709
+ if idx not in or_covered:
710
+ type_arms[idx].append((case, alt, as_name, as_raw_name))
711
+ else:
712
+ raise CodeGenError(
713
+ f"Unsupported or-pattern alternative: {type(alt).__name__}")
714
+
715
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
716
+ for idx in type_arms:
717
+ type_arms[idx].append((case, pattern, as_name, as_raw_name))
718
+
719
+ else:
720
+ raise CodeGenError(
721
+ f"Unsupported pattern in guarded union match: {type(pattern).__name__}")
722
+
723
+ # Truncate each type's arm list after the first truly unguarded arm
724
+ # (anything after an unguarded arm is unreachable).
725
+ # Arms with union field guards are effectively guarded even if guard is None.
726
+ for idx in type_arms:
727
+ truncated: list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]] = []
728
+ for entry in type_arms[idx]:
729
+ truncated.append(entry)
730
+ pat = entry[1]
731
+ has_field_guard = (isinstance(pat, TpyClassPattern)
732
+ and any(self._sub_has_union_field_guard(sub)
733
+ for _, sub in pat.keywords))
734
+ if entry[0].guard is None and not has_field_guard:
735
+ break
736
+ type_arms[idx] = truncated
737
+
738
+ # Types whose arms are ALL wildcards/captures can share default:.
739
+ # All such indices have identical arm lists because wildcard/capture arms
740
+ # are always broadcast to every index uniformly during collection above.
741
+ default_indices: set[int] = set()
742
+ default_arms: list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]] | None = None
743
+ for idx, arms in type_arms.items():
744
+ if arms and all(isinstance(a[1], (TpyWildcardPattern, TpyCapturePattern))
745
+ for a in arms):
746
+ default_indices.add(idx)
747
+ if default_arms is None:
748
+ default_arms = arms
749
+
750
+ out.write(f"{indent}switch ({va.index_expr()}) {{\n")
751
+
752
+ for idx in range(len(full_members)):
753
+ if idx in default_indices:
754
+ continue
755
+ arms = type_arms[idx]
756
+ if not arms:
757
+ continue
758
+
759
+ out.write(f"{indent}case {idx}: {{\n")
760
+
761
+ # Extract variant value once for this case block
762
+ needs_extraction = any(
763
+ isinstance(a[1], TpyClassPattern)
764
+ and (a[1].keywords or a[0].type_facts or a[2] is not None)
765
+ for a in arms
766
+ )
767
+ case_var = f"__case_{idx}"
768
+ if needs_extraction:
769
+ out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
770
+
771
+ use_scope = len(arms) > 1
772
+ for arm_case, arm_pattern, as_name, as_raw_name in arms:
773
+ self.ctx.emit_source_comment(out, arm_case.loc, inner)
774
+ self._gen_guarded_switch_arm_action(
775
+ out, arm_case, arm_pattern, case_var if needs_extraction else None,
776
+ as_name, as_raw_name, inner, inner2, end_label,
777
+ needs_scope=use_scope,
778
+ )
779
+
780
+ out.write(f"{inner}break;\n")
781
+ out.write(f"{indent}}}\n")
782
+
783
+ if default_indices and default_arms:
784
+ out.write(f"{indent}default: {{\n")
785
+ use_scope = len(default_arms) > 1
786
+ for arm_case, arm_pattern, as_name, as_raw_name in default_arms:
787
+ self.ctx.emit_source_comment(out, arm_case.loc, inner)
788
+ self._gen_guarded_switch_arm_action(
789
+ out, arm_case, arm_pattern, None,
790
+ as_name, as_raw_name, inner, inner2, end_label,
791
+ needs_scope=use_scope,
792
+ )
793
+ out.write(f"{inner}break;\n")
794
+ out.write(f"{indent}}}\n")
795
+
796
+ out.write(f"{indent}}}\n")
797
+ out.write(f"{end_label}:;\n")
798
+
799
+ def _gen_guarded_switch_arm_action(
800
+ self, out: TextIO, arm_case: TpyMatchCase, pattern: TpyPattern,
801
+ case_var: str | None,
802
+ as_name: str | None, as_raw_name: str | None,
803
+ inner: str, inner2: str, end_label: str,
804
+ needs_scope: bool = False,
805
+ ) -> None:
806
+ """Emit a single arm action within a switch case block.
807
+
808
+ When needs_scope is True, wraps bindings+body in { } to avoid name
809
+ conflicts with other arms in the same case block.
810
+ """
811
+ bind_indent = inner2 if needs_scope else inner
812
+
813
+ if needs_scope:
814
+ out.write(f"{inner}{{\n")
815
+
816
+ # Emit guarded/unguarded body with goto
817
+ has_narrowing = isinstance(pattern, TpyClassPattern)
818
+ saved: dict[str, str | None] = {}
819
+ guard = arm_case.guard
820
+
821
+ # Union field guards generate implicit conditions even without explicit guards
822
+ field_conds = (self._record_field_conditions(pattern, case_var)
823
+ if isinstance(pattern, TpyClassPattern) and case_var else [])
824
+
825
+ # Emit non-union bindings before the condition (guards may reference them).
826
+ # Union field bindings (std::get) must go after the holds_alternative check.
827
+ if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
828
+ if isinstance(pattern, TpyCapturePattern):
829
+ self._emit_binding(
830
+ out, escape_cpp_name(pattern.name), pattern.name,
831
+ "__match_subject", bind_indent)
832
+ self._emit_binding(out, as_name, as_raw_name, "__match_subject", bind_indent)
833
+ elif isinstance(pattern, TpyClassPattern) and not field_conds:
834
+ if pattern.keywords:
835
+ self._gen_match_field_bindings(out, pattern, case_var, bind_indent)
836
+ self._emit_binding(out, as_name, as_raw_name, case_var, bind_indent)
837
+ elif not isinstance(pattern, TpyClassPattern):
838
+ raise CodeGenError(
839
+ f"Unsupported pattern in guarded switch arm: {type(pattern).__name__}")
840
+
841
+ if guard is not None or field_conds:
842
+ cond_parts: list[str] = list(field_conds)
843
+ if guard is not None:
844
+ guard_code = self.expressions.gen_expr(guard)
845
+ self.ctx.temps.flush(out, bind_indent)
846
+ cond_parts.append(guard_code)
847
+ out.write(f"{bind_indent}if ({' && '.join(cond_parts)}) {{\n")
848
+ # Emit union field bindings inside the condition block
849
+ body_indent = INDENT * (self.ctx.indent_level + (3 if needs_scope else 2))
850
+ if isinstance(pattern, TpyClassPattern) and field_conds:
851
+ if pattern.keywords:
852
+ self._gen_match_field_bindings(out, pattern, case_var, body_indent)
853
+ self._emit_binding(out, as_name, as_raw_name, case_var, body_indent)
854
+ if has_narrowing:
855
+ saved = self._apply_narrowing(arm_case.type_facts, case_var)
856
+ extra = 3 if needs_scope else 2
857
+ self.ctx.indent_level += extra
858
+ self._emit_case_body(out, arm_case.body, arm_case.type_facts)
859
+ out.write(f"{INDENT * self.ctx.indent_level}goto {end_label};\n")
860
+ self.ctx.indent_level -= extra
861
+ if has_narrowing:
862
+ self.stmts.ctx.restore_narrowed_vars(saved)
863
+ out.write(f"{bind_indent}}}\n")
864
+ else:
865
+ if has_narrowing:
866
+ saved = self._apply_narrowing(arm_case.type_facts, case_var)
867
+ extra = 2 if needs_scope else 1
868
+ self.ctx.indent_level += extra
869
+ self._emit_case_body(out, arm_case.body, arm_case.type_facts)
870
+ out.write(f"{INDENT * self.ctx.indent_level}goto {end_label};\n")
871
+ self.ctx.indent_level -= extra
872
+ if has_narrowing:
873
+ self.stmts.ctx.restore_narrowed_vars(saved)
874
+
875
+ if needs_scope:
876
+ out.write(f"{inner}}}\n")
877
+
878
+ def _apply_narrowing(
879
+ self, type_facts: dict[str, TpyType] | None, case_var: str | None,
880
+ ) -> dict[str, str | None]:
881
+ """Apply narrowing facts and return saved state for later restoration."""
882
+ saved: dict[str, str | None] = {}
883
+ if type_facts:
884
+ for var_name in type_facts:
885
+ saved[var_name] = self.ctx.narrowed_vars.get(var_name)
886
+ self.ctx.narrowed_vars[var_name] = case_var
887
+ return saved
888
+
889
+ def _emit_case_body(
890
+ self, out: TextIO, body: list['TpyStmt'],
891
+ type_facts: dict[str, TpyType] | None = None,
892
+ ) -> None:
893
+ """Emit a case body with literal_facts/protocol_narrowings bounded to the case scope.
894
+
895
+ Persistent narrowings introduced inside a body (e.g. `assert isinstance(x, P)`)
896
+ would otherwise bleed into later cases or post-match code. Snapshotting and
897
+ restoring around body emission keeps them scoped to this case.
898
+
899
+ When type_facts is provided, LiteralType facts are pushed for dead-branch
900
+ elimination. narrowed_vars std::get extractions are managed separately by
901
+ the caller via _apply_narrowing (they depend on a case_var).
902
+ """
903
+ proto_saved = self.ctx.save_protocol_narrowings()
904
+ lit_saved = self.ctx.save_literal_facts()
905
+ # Persistent isinstance aliases (assert / early-return) emitted inside
906
+ # the case body live only in this case's C++ scope; restore both the
907
+ # alias-name set and narrowed_vars so subsequent cases / post-match
908
+ # code don't reference out-of-scope aliases.
909
+ narrowed_saved = dict(self.ctx.narrowed_vars)
910
+ alias_saved = self.ctx.declared_persistent_aliases.copy()
911
+ if type_facts:
912
+ for var_name, ty in type_facts.items():
913
+ if isinstance(ty, LiteralType):
914
+ self.ctx.literal_facts[var_name] = ty
915
+ try:
916
+ # Resumable-frame `match` (H1): when a suspending match is
917
+ # being emitted, this arm's body is not emitted inline -- it
918
+ # lives in the state machine and is routed back through the
919
+ # resumable walker. Identify the arm by its body's identity.
920
+ arm_emitter = self.ctx.resumable_arm_emitter
921
+ arm_bb = (self.ctx.resumable_arm_bb_by_body.get(id(body))
922
+ if arm_emitter is not None else None)
923
+ if arm_emitter is not None and arm_bb is not None:
924
+ arm_emitter(arm_bb)
925
+ else:
926
+ for s in body:
927
+ self.stmts.gen_stmt(out, s)
928
+ finally:
929
+ self.ctx.narrowed_vars = narrowed_saved
930
+ self.ctx.declared_persistent_aliases = alias_saved
931
+ self.ctx.restore_literal_facts(lit_saved)
932
+ self.ctx.restore_protocol_narrowings(proto_saved)
933
+
934
+ def _gen_match_if_elif(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
935
+ """Generate match/case as an if/elif/else chain (for str and float subjects)."""
936
+ inner = INDENT * (self.ctx.indent_level + 1)
937
+
938
+ for i, case in enumerate(stmt.cases):
939
+ self.ctx.emit_source_comment(out, case.loc, indent)
940
+ keyword = "if" if i == 0 else "} else if"
941
+ pattern = case.pattern
942
+ guard = case.guard
943
+
944
+ if isinstance(pattern, TpyLiteralPattern):
945
+ cond = self._gen_literal_cond(pattern)
946
+ if guard is not None:
947
+ guard_code = self.expressions.gen_expr(guard)
948
+ self.ctx.temps.flush(out, indent)
949
+ cond = f"{cond} && {guard_code}"
950
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
951
+ self.ctx.indent_level += 1
952
+ self._emit_case_body(out, case.body, case.type_facts)
953
+ self.ctx.indent_level -= 1
954
+
955
+ elif isinstance(pattern, TpyOrPattern):
956
+ conds = []
957
+ for alt in pattern.patterns:
958
+ if isinstance(alt, TpyLiteralPattern):
959
+ conds.append(self._gen_literal_cond(alt))
960
+ else:
961
+ raise CodeGenError(f"Unsupported or-pattern alternative: {type(alt).__name__}")
962
+ cond = " || ".join(conds)
963
+ if guard is not None:
964
+ guard_code = self.expressions.gen_expr(guard)
965
+ self.ctx.temps.flush(out, indent)
966
+ cond = f"({cond}) && {guard_code}"
967
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
968
+ self.ctx.indent_level += 1
969
+ self._emit_case_body(out, case.body, case.type_facts)
970
+ self.ctx.indent_level -= 1
971
+
972
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
973
+ # Emit capture binding before the guard check so the guard
974
+ # can reference the captured variable.
975
+ if isinstance(pattern, TpyCapturePattern) and guard is not None:
976
+ if i > 0:
977
+ out.write(f"{indent}}}\n")
978
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", indent)
979
+ guard_code = self.expressions.gen_expr(guard)
980
+ self.ctx.temps.flush(out, indent)
981
+ out.write(f"{indent}if ({guard_code}) {{\n")
982
+ elif guard is not None:
983
+ guard_code = self.expressions.gen_expr(guard)
984
+ self.ctx.temps.flush(out, indent)
985
+ out.write(f"{indent}{keyword} ({guard_code}) {{\n")
986
+ elif i == 0:
987
+ out.write(f"{indent}{{\n")
988
+ else:
989
+ out.write(f"{indent}}} else {{\n")
990
+ if isinstance(pattern, TpyCapturePattern) and guard is None:
991
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
992
+ self.ctx.indent_level += 1
993
+ self._emit_case_body(out, case.body, case.type_facts)
994
+ self.ctx.indent_level -= 1
995
+
996
+ elif isinstance(pattern, TpyAsPattern):
997
+ inner_pat = pattern.pattern
998
+ as_name = escape_cpp_name(pattern.name)
999
+ if isinstance(inner_pat, (TpyLiteralPattern, TpyValuePattern)):
1000
+ if isinstance(inner_pat, TpyLiteralPattern):
1001
+ cond = self._gen_literal_cond(inner_pat)
1002
+ else:
1003
+ val_code = self.expressions.gen_expr(inner_pat.expr)
1004
+ self.ctx.temps.flush(out, indent)
1005
+ cond = f"__match_subject == {val_code}"
1006
+ if guard is not None:
1007
+ # Guard may reference as-variable; split into match + bind + guard
1008
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1009
+ self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
1010
+ guard_code = self.expressions.gen_expr(guard)
1011
+ self.ctx.temps.flush(out, inner)
1012
+ out.write(f"{inner}if ({guard_code}) {{\n")
1013
+ self.ctx.indent_level += 2
1014
+ self._emit_case_body(out, case.body, case.type_facts)
1015
+ self.ctx.indent_level -= 2
1016
+ out.write(f"{inner}}}\n")
1017
+ else:
1018
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1019
+ self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
1020
+ self.ctx.indent_level += 1
1021
+ self._emit_case_body(out, case.body, case.type_facts)
1022
+ self.ctx.indent_level -= 1
1023
+ elif isinstance(inner_pat, (TpyWildcardPattern, TpyCapturePattern)):
1024
+ if guard is not None:
1025
+ # Emit binding before guard (like capture+guard path)
1026
+ if i > 0:
1027
+ out.write(f"{indent}}}\n")
1028
+ self._emit_binding(out, as_name, pattern.name, "__match_subject", indent)
1029
+ if isinstance(inner_pat, TpyCapturePattern):
1030
+ self._emit_binding(out, escape_cpp_name(inner_pat.name), inner_pat.name, "__match_subject", indent)
1031
+ guard_code = self.expressions.gen_expr(guard)
1032
+ self.ctx.temps.flush(out, indent)
1033
+ out.write(f"{indent}if ({guard_code}) {{\n")
1034
+ else:
1035
+ if i == 0:
1036
+ out.write(f"{indent}{{\n")
1037
+ else:
1038
+ out.write(f"{indent}}} else {{\n")
1039
+ self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
1040
+ if isinstance(inner_pat, TpyCapturePattern):
1041
+ self._emit_binding(out, escape_cpp_name(inner_pat.name), inner_pat.name, "__match_subject", inner)
1042
+ self.ctx.indent_level += 1
1043
+ self._emit_case_body(out, case.body, case.type_facts)
1044
+ self.ctx.indent_level -= 1
1045
+ else:
1046
+ raise CodeGenError(f"Unsupported as-pattern inner: {type(inner_pat).__name__}")
1047
+
1048
+ else:
1049
+ raise CodeGenError(f"Unsupported match pattern: {type(pattern).__name__}")
1050
+
1051
+ out.write(f"{indent}}}\n")
1052
+
1053
+ def _should_switch_str(self, stmt: TpyMatch) -> bool:
1054
+ """Check if a string match has enough unguarded literal cases for switch dispatch."""
1055
+ count = 0
1056
+ for case in stmt.cases:
1057
+ if case.guard is not None:
1058
+ continue
1059
+ pat = case.pattern
1060
+ if isinstance(pat, TpyAsPattern):
1061
+ pat = pat.pattern
1062
+ if isinstance(pat, TpyLiteralPattern) and isinstance(pat.value, str):
1063
+ count += 1
1064
+ elif isinstance(pat, TpyOrPattern):
1065
+ if all(isinstance(a, TpyLiteralPattern) and isinstance(a.value, str)
1066
+ for a in pat.patterns):
1067
+ count += len(pat.patterns)
1068
+ return count >= STRING_SWITCH_THRESHOLD
1069
+
1070
+ def _gen_match_switch_str(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
1071
+ """Generate optimized switch-based dispatch for string match/case."""
1072
+ inner = INDENT * (self.ctx.indent_level + 1)
1073
+ deep = INDENT * (self.ctx.indent_level + 2)
1074
+
1075
+ self.ctx.match_counter += 1
1076
+ end_label = f"__match_end_{self.ctx.match_counter}"
1077
+
1078
+ # Partition cases into guarded literals, unguarded literals, and trailing
1079
+ guarded: list[TpyMatchCase] = []
1080
+ # Each unguarded entry: (case, list_of_string_values)
1081
+ unguarded: list[tuple[TpyMatchCase, list[str]]] = []
1082
+ trailing: list[TpyMatchCase] = []
1083
+
1084
+ for case in stmt.cases:
1085
+ pat = case.pattern
1086
+ if isinstance(pat, TpyAsPattern):
1087
+ pat = pat.pattern
1088
+ is_str_lit = isinstance(pat, TpyLiteralPattern) and isinstance(pat.value, str)
1089
+ is_str_or = (isinstance(pat, TpyOrPattern) and
1090
+ all(isinstance(a, TpyLiteralPattern) and isinstance(a.value, str)
1091
+ for a in pat.patterns))
1092
+ if is_str_lit or is_str_or:
1093
+ if case.guard is not None:
1094
+ guarded.append(case)
1095
+ else:
1096
+ strs = [pat.value] if is_str_lit else [a.value for a in pat.patterns]
1097
+ unguarded.append((case, strs))
1098
+ else:
1099
+ trailing.append(case)
1100
+
1101
+ # Collect all strings and find best discriminator
1102
+ all_strings = []
1103
+ for _, strs in unguarded:
1104
+ all_strings.extend(strs)
1105
+ kind, param, _buckets = find_best_discriminator(all_strings)
1106
+
1107
+ # Build bucket -> [(case, string_value)] mapping, preserving arm order
1108
+ bucket_entries: dict[int, list[tuple[TpyMatchCase, str]]] = defaultdict(list)
1109
+ for case, strs in unguarded:
1110
+ for s in strs:
1111
+ key = len(s) if kind == "length" else ord(s[param])
1112
+ bucket_entries[key].append((case, s))
1113
+
1114
+ # Emit guarded string literal arms first (pre-switch, in original order)
1115
+ for case in guarded:
1116
+ self.ctx.emit_source_comment(out, case.loc, indent)
1117
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1118
+ if isinstance(pattern, TpyLiteralPattern):
1119
+ cond = self._gen_literal_cond(pattern)
1120
+ else:
1121
+ # Or-pattern
1122
+ conds = [self._gen_literal_cond(a) for a in pattern.patterns]
1123
+ cond = " || ".join(conds)
1124
+ guard_code = self.expressions.gen_expr(case.guard)
1125
+ self.ctx.temps.flush(out, indent)
1126
+ is_or = isinstance(pattern, TpyOrPattern)
1127
+ cond = f"({cond}) && {guard_code}" if is_or else f"{cond} && {guard_code}"
1128
+ out.write(f"{indent}if ({cond}) {{\n")
1129
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1130
+ self.ctx.indent_level += 1
1131
+ self._emit_case_body(out, case.body, case.type_facts)
1132
+ self.ctx.indent_level -= 1
1133
+ out.write(f"{inner}goto {end_label};\n")
1134
+ out.write(f"{indent}}}\n")
1135
+
1136
+ # Emit switch on discriminator
1137
+ # For char_at, wrap in an if-guard so short strings skip the switch
1138
+ sw_indent = indent
1139
+ sw_inner = inner
1140
+ sw_deep = deep
1141
+ if kind == "char_at":
1142
+ out.write(f"{indent}if (__match_subject.size() >= {param + 1}) {{\n")
1143
+ sw_indent = inner
1144
+ sw_inner = deep
1145
+ sw_deep = INDENT * (self.ctx.indent_level + 3)
1146
+ out.write(f"{sw_indent}switch (static_cast<unsigned char>(__match_subject[{param}])) {{\n")
1147
+ else:
1148
+ out.write(f"{indent}switch (__match_subject.size()) {{\n")
1149
+
1150
+ for disc_value in sorted(bucket_entries.keys()):
1151
+ entries = bucket_entries[disc_value]
1152
+ if kind == "char_at":
1153
+ ch = chr(disc_value)
1154
+ out.write(f"{sw_indent}case '{escape_cpp_char(ch)}': {{\n")
1155
+ else:
1156
+ out.write(f"{sw_indent}case {disc_value}: {{\n")
1157
+ for case, string_val in entries:
1158
+ self.ctx.emit_source_comment(out, case.loc, sw_inner)
1159
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1160
+ out.write(f'{sw_inner}if (__match_subject == {cpp_string_literal_expr(string_val)}) {{\n')
1161
+ self._emit_binding(out, as_name, as_raw, "__match_subject", sw_deep)
1162
+ self.ctx.indent_level += (3 if kind == "char_at" else 2)
1163
+ self._emit_case_body(out, case.body, case.type_facts)
1164
+ self.ctx.indent_level -= (3 if kind == "char_at" else 2)
1165
+ out.write(f"{sw_deep}goto {end_label};\n")
1166
+ out.write(f"{sw_inner}}}\n")
1167
+ out.write(f"{sw_inner}break;\n")
1168
+ out.write(f"{sw_indent}}}\n")
1169
+
1170
+ out.write(f"{sw_indent}}}\n") # close switch
1171
+ if kind == "char_at":
1172
+ out.write(f"{indent}}}\n") # close if-guard
1173
+
1174
+ # Emit trailing arms (wildcard, capture, etc.) in a block scope
1175
+ # to prevent goto from crossing variable declarations
1176
+ if trailing:
1177
+ out.write(f"{indent}{{\n")
1178
+ for case in trailing:
1179
+ self.ctx.emit_source_comment(out, case.loc, inner)
1180
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1181
+ if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1182
+ if isinstance(pattern, TpyCapturePattern):
1183
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
1184
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1185
+ if case.guard is not None:
1186
+ guard_code = self.expressions.gen_expr(case.guard)
1187
+ self.ctx.temps.flush(out, inner)
1188
+ out.write(f"{inner}if ({guard_code}) {{\n")
1189
+ self.ctx.indent_level += 2
1190
+ self._emit_case_body(out, case.body, case.type_facts)
1191
+ self.ctx.indent_level -= 2
1192
+ out.write(f"{inner}}}\n")
1193
+ else:
1194
+ self.ctx.indent_level += 1
1195
+ self._emit_case_body(out, case.body, case.type_facts)
1196
+ self.ctx.indent_level -= 1
1197
+ else:
1198
+ raise CodeGenError(
1199
+ f"Unsupported trailing pattern in string switch: {type(pattern).__name__}"
1200
+ )
1201
+ out.write(f"{indent}}}\n")
1202
+
1203
+ out.write(f"{indent}{end_label}:;\n")
1204
+
1205
+ def _gen_match_if_elif_record(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
1206
+ """Generate match/case as if/elif chain for concrete record subjects (no guards)."""
1207
+ inner = INDENT * (self.ctx.indent_level + 1)
1208
+
1209
+ for i, case in enumerate(stmt.cases):
1210
+ self.ctx.emit_source_comment(out, case.loc, indent)
1211
+ keyword = "if" if i == 0 else "} else if"
1212
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1213
+
1214
+ if isinstance(pattern, TpyClassPattern):
1215
+ conds = self._record_field_conditions(pattern)
1216
+ if conds:
1217
+ out.write(f"{indent}{keyword} ({' && '.join(conds)}) {{\n")
1218
+ elif i == 0:
1219
+ out.write(f"{indent}{{\n")
1220
+ else:
1221
+ out.write(f"{indent}}} else {{\n")
1222
+ self._gen_match_field_bindings(out, pattern, "__match_subject", inner)
1223
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1224
+ self.ctx.indent_level += 1
1225
+ self._emit_case_body(out, case.body, case.type_facts)
1226
+ self.ctx.indent_level -= 1
1227
+
1228
+ elif isinstance(pattern, TpyOrPattern):
1229
+ or_parts: list[str] = []
1230
+ for alt in pattern.patterns:
1231
+ if isinstance(alt, TpyClassPattern):
1232
+ alt_conds = self._record_field_conditions(alt)
1233
+ if alt_conds:
1234
+ or_parts.append("(" + " && ".join(alt_conds) + ")")
1235
+ elif isinstance(alt, TpyWildcardPattern):
1236
+ or_parts.clear()
1237
+ break
1238
+ else:
1239
+ raise CodeGenError(
1240
+ f"Unsupported or-pattern alternative for record: "
1241
+ f"{type(alt).__name__}"
1242
+ )
1243
+ if or_parts:
1244
+ out.write(f"{indent}{keyword} ({' || '.join(or_parts)}) {{\n")
1245
+ elif i == 0:
1246
+ out.write(f"{indent}{{\n")
1247
+ else:
1248
+ out.write(f"{indent}}} else {{\n")
1249
+ self.ctx.indent_level += 1
1250
+ self._emit_case_body(out, case.body, case.type_facts)
1251
+ self.ctx.indent_level -= 1
1252
+
1253
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1254
+ if i == 0:
1255
+ out.write(f"{indent}{{\n")
1256
+ else:
1257
+ out.write(f"{indent}}} else {{\n")
1258
+ if isinstance(pattern, TpyCapturePattern):
1259
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
1260
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1261
+ self.ctx.indent_level += 1
1262
+ self._emit_case_body(out, case.body, case.type_facts)
1263
+ self.ctx.indent_level -= 1
1264
+
1265
+ else:
1266
+ raise CodeGenError(f"Unsupported match pattern for record: {type(pattern).__name__}")
1267
+
1268
+ out.write(f"{indent}}}\n")
1269
+
1270
+ def _gen_match_guarded_record(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
1271
+ """Generate match/case for record subjects with guards using standalone ifs + goto."""
1272
+ inner = INDENT * (self.ctx.indent_level + 1)
1273
+ self.ctx.match_counter += 1
1274
+ end_label = f"__match_end_{self.ctx.match_counter}"
1275
+
1276
+ for i, case in enumerate(stmt.cases):
1277
+ self.ctx.emit_source_comment(out, case.loc, indent)
1278
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1279
+ guard = case.guard
1280
+
1281
+ if isinstance(pattern, TpyClassPattern):
1282
+ conds = self._record_field_conditions(pattern)
1283
+ if conds:
1284
+ out.write(f"{indent}if ({' && '.join(conds)}) {{\n")
1285
+ else:
1286
+ out.write(f"{indent}{{\n")
1287
+ self._gen_match_field_bindings(out, pattern, "__match_subject", inner)
1288
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1289
+ if guard is not None:
1290
+ guard_code = self.expressions.gen_expr(guard)
1291
+ self.ctx.temps.flush(out, inner)
1292
+ out.write(f"{inner}if ({guard_code}) {{\n")
1293
+ self.ctx.indent_level += 2
1294
+ self._emit_case_body(out, case.body, case.type_facts)
1295
+ self.ctx.indent_level -= 2
1296
+ out.write(f"{inner} goto {end_label};\n")
1297
+ out.write(f"{inner}}}\n")
1298
+ else:
1299
+ self.ctx.indent_level += 1
1300
+ self._emit_case_body(out, case.body, case.type_facts)
1301
+ self.ctx.indent_level -= 1
1302
+ out.write(f"{inner}goto {end_label};\n")
1303
+ out.write(f"{indent}}}\n")
1304
+
1305
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1306
+ if isinstance(pattern, TpyCapturePattern):
1307
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", indent)
1308
+ self._emit_binding(out, as_name, as_raw, "__match_subject", indent)
1309
+ if guard is not None:
1310
+ guard_code = self.expressions.gen_expr(guard)
1311
+ self.ctx.temps.flush(out, indent)
1312
+ out.write(f"{indent}if ({guard_code}) {{\n")
1313
+ self.ctx.indent_level += 1
1314
+ self._emit_case_body(out, case.body, case.type_facts)
1315
+ self.ctx.indent_level -= 1
1316
+ out.write(f"{inner}goto {end_label};\n")
1317
+ out.write(f"{indent}}}\n")
1318
+ else:
1319
+ out.write(f"{indent}{{\n")
1320
+ self.ctx.indent_level += 1
1321
+ self._emit_case_body(out, case.body, case.type_facts)
1322
+ self.ctx.indent_level -= 1
1323
+ out.write(f"{indent}}}\n")
1324
+
1325
+ elif isinstance(pattern, TpyOrPattern):
1326
+ or_parts: list[str] = []
1327
+ for alt in pattern.patterns:
1328
+ if isinstance(alt, TpyClassPattern):
1329
+ alt_conds = self._record_field_conditions(alt)
1330
+ if alt_conds:
1331
+ or_parts.append("(" + " && ".join(alt_conds) + ")")
1332
+ elif isinstance(alt, TpyWildcardPattern):
1333
+ or_parts.clear()
1334
+ break
1335
+ else:
1336
+ raise CodeGenError(
1337
+ f"Unsupported or-pattern alternative for record: "
1338
+ f"{type(alt).__name__}"
1339
+ )
1340
+ if or_parts:
1341
+ cond = " || ".join(or_parts)
1342
+ if guard is not None:
1343
+ guard_code = self.expressions.gen_expr(guard)
1344
+ self.ctx.temps.flush(out, indent)
1345
+ cond = f"({cond}) && {guard_code}"
1346
+ out.write(f"{indent}if ({cond}) {{\n")
1347
+ else:
1348
+ if guard is not None:
1349
+ guard_code = self.expressions.gen_expr(guard)
1350
+ self.ctx.temps.flush(out, indent)
1351
+ out.write(f"{indent}if ({guard_code}) {{\n")
1352
+ else:
1353
+ out.write(f"{indent}{{\n")
1354
+ self.ctx.indent_level += 1
1355
+ self._emit_case_body(out, case.body, case.type_facts)
1356
+ self.ctx.indent_level -= 1
1357
+ out.write(f"{inner}goto {end_label};\n")
1358
+ out.write(f"{indent}}}\n")
1359
+
1360
+ else:
1361
+ raise CodeGenError(f"Unsupported match pattern for record: {type(pattern).__name__}")
1362
+
1363
+ out.write(f"{indent}{end_label}:;\n")
1364
+
1365
+
1366
+ # ------------------------------------------------------------------
1367
+ # Optimized Optional match: hoist null check, dispatch inner
1368
+ # ------------------------------------------------------------------
1369
+
1370
+ def _partition_optional_cases(
1371
+ self, cases: list['TpyMatchCase'],
1372
+ ) -> tuple[list['TpyMatchCase'], list['TpyMatchCase']] | None:
1373
+ """Split cases into (none_cases, inner_cases) if None arms form a prefix.
1374
+
1375
+ Returns None if the optimization cannot be applied:
1376
+ - None arms don't form a contiguous prefix
1377
+ - An or-pattern mixes None and non-None alternatives
1378
+ - A None arm has a guard (guard failure needs fallthrough to later arms)
1379
+ """
1380
+ none_cases: list[TpyMatchCase] = []
1381
+ inner_cases: list[TpyMatchCase] = []
1382
+ seen_inner = False
1383
+
1384
+ for case in cases:
1385
+ pat = case.pattern
1386
+ if isinstance(pat, TpyAsPattern):
1387
+ pat = pat.pattern
1388
+
1389
+ # Or-pattern mixing None and non-None -- bail out
1390
+ if isinstance(pat, TpyOrPattern):
1391
+ has_none = any(
1392
+ isinstance(a, TpyLiteralPattern) and a.value is None
1393
+ for a in pat.patterns
1394
+ )
1395
+ has_other = any(
1396
+ not (isinstance(a, TpyLiteralPattern) and a.value is None)
1397
+ for a in pat.patterns
1398
+ )
1399
+ if has_none and has_other:
1400
+ return None
1401
+ if has_none:
1402
+ if seen_inner:
1403
+ return None
1404
+ if case.guard is not None:
1405
+ return None
1406
+ none_cases.append(case)
1407
+ else:
1408
+ seen_inner = True
1409
+ inner_cases.append(case)
1410
+ continue
1411
+
1412
+ is_none = isinstance(pat, TpyLiteralPattern) and pat.value is None
1413
+ if is_none:
1414
+ if seen_inner:
1415
+ return None
1416
+ # Guarded None arm needs fallthrough to later arms on guard failure
1417
+ if case.guard is not None:
1418
+ return None
1419
+ none_cases.append(case)
1420
+ else:
1421
+ seen_inner = True
1422
+ inner_cases.append(case)
1423
+
1424
+ if not inner_cases:
1425
+ return None
1426
+ return none_cases, inner_cases
1427
+
1428
+ def _gen_match_optimized_optional(
1429
+ self, out: TextIO, stmt: TpyMatch, subject_type: OptionalType,
1430
+ none_cases: list['TpyMatchCase'], inner_cases: list['TpyMatchCase'],
1431
+ indent: str,
1432
+ ) -> None:
1433
+ """Generate optimized Optional match: if (null) { ... } else { dispatch }."""
1434
+ inner = INDENT * (self.ctx.indent_level + 1)
1435
+ uses_ptr = subject_type.uses_pointer_repr()
1436
+ null_cond = "__match_subject == nullptr" if uses_ptr else "!__match_subject.has_value()"
1437
+
1438
+ # --- None branch ---
1439
+ has_value_cond = "__match_subject != nullptr" if uses_ptr else "__match_subject.has_value()"
1440
+ if not none_cases:
1441
+ # No None arms -- just guard on has_value, no else
1442
+ out.write(f"{indent}if ({has_value_cond}) {{\n")
1443
+ elif len(none_cases) == 1 and none_cases[0].guard is None:
1444
+ # Simple: single unguarded None arm
1445
+ case = none_cases[0]
1446
+ self.ctx.emit_source_comment(out, case.loc, indent)
1447
+ out.write(f"{indent}if ({null_cond}) {{\n")
1448
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1449
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1450
+ self.ctx.indent_level += 1
1451
+ self._emit_case_body(out, case.body, case.type_facts)
1452
+ self.ctx.indent_level -= 1
1453
+ else:
1454
+ # Multiple or guarded None arms: guard chain inside null block
1455
+ self.ctx.emit_source_comment(out, none_cases[0].loc, indent)
1456
+ out.write(f"{indent}if ({null_cond}) {{\n")
1457
+ for j, case in enumerate(none_cases):
1458
+ if j > 0:
1459
+ self.ctx.emit_source_comment(out, case.loc, inner)
1460
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1461
+ if case.guard is not None:
1462
+ guard_code = self.expressions.gen_expr(case.guard)
1463
+ self.ctx.temps.flush(out, inner)
1464
+ kw = "if" if j == 0 else "} else if"
1465
+ out.write(f"{inner}{kw} ({guard_code}) {{\n")
1466
+ else:
1467
+ if j == 0:
1468
+ pass # body goes directly in null block
1469
+ else:
1470
+ out.write(f"{inner}}} else {{\n")
1471
+ deep = INDENT * (self.ctx.indent_level + 2) if case.guard is not None or j > 0 else inner
1472
+ self._emit_binding(out, as_name, as_raw, "__match_subject", deep)
1473
+ extra = 2 if case.guard is not None or j > 0 else 1
1474
+ self.ctx.indent_level += extra
1475
+ self._emit_case_body(out, case.body, case.type_facts)
1476
+ self.ctx.indent_level -= extra
1477
+ if any(c.guard is not None for c in none_cases):
1478
+ out.write(f"{inner}}}\n")
1479
+
1480
+ # --- Inner value dispatch ---
1481
+ if none_cases:
1482
+ out.write(f"{indent}}} else {{\n")
1483
+ deref = "(*__match_subject)"
1484
+ out.write(f"{inner}auto& __match_inner = {deref};\n")
1485
+
1486
+ inner_type = subject_type.inner
1487
+ if is_enum_type(inner_type):
1488
+ groups = self._group_switch_arms(inner_cases, kind="enum")
1489
+ self.ctx.indent_level += 1
1490
+ self._emit_switch_groups(out, groups, inner, subject_expr="__match_inner")
1491
+ self.ctx.indent_level -= 1
1492
+ elif is_fixed_int_type(inner_type) or is_bool_type(inner_type):
1493
+ groups = self._group_switch_arms(inner_cases, kind="primitive")
1494
+ self.ctx.indent_level += 1
1495
+ self._emit_switch_groups(out, groups, inner, subject_expr="__match_inner")
1496
+ self.ctx.indent_level -= 1
1497
+ elif isinstance(inner_type, NominalType) and inner_type.is_user_record:
1498
+ self._emit_optional_inner_record(out, inner_cases, inner)
1499
+ else:
1500
+ # str, float, other: if/elif chain on __match_inner
1501
+ self._emit_optional_inner_if_elif(out, inner_cases, inner)
1502
+
1503
+ out.write(f"{indent}}}\n")
1504
+
1505
+ def _emit_optional_inner_record(
1506
+ self, out: TextIO, cases: list['TpyMatchCase'], indent: str,
1507
+ ) -> None:
1508
+ """Emit if/elif chain for record patterns on dereferenced Optional."""
1509
+ inner = INDENT * (self.ctx.indent_level + 2)
1510
+ subject_expr = "__match_inner"
1511
+
1512
+ for i, case in enumerate(cases):
1513
+ self.ctx.emit_source_comment(out, case.loc, indent)
1514
+ keyword = "if" if i == 0 else "} else if"
1515
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1516
+ guard = case.guard
1517
+
1518
+ if isinstance(pattern, TpyClassPattern):
1519
+ field_conds = self._record_field_conditions(pattern, subject_expr)
1520
+ cond = " && ".join(field_conds) if field_conds else "true"
1521
+ if guard is not None:
1522
+ guard_code = self.expressions.gen_expr(guard)
1523
+ self.ctx.temps.flush(out, indent)
1524
+ cond = f"{cond} && {guard_code}" if field_conds else guard_code
1525
+ if not field_conds and guard is None:
1526
+ # Always-matching class pattern -> else
1527
+ if i == 0:
1528
+ out.write(f"{indent}{{\n")
1529
+ else:
1530
+ out.write(f"{indent}}} else {{\n")
1531
+ else:
1532
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1533
+ # Emit field bindings
1534
+ self._gen_match_field_bindings(out, pattern, subject_expr, inner)
1535
+ self._emit_binding(out, as_name, as_raw, subject_expr, inner)
1536
+ self.ctx.indent_level += 2
1537
+ self._emit_case_body(out, case.body, case.type_facts)
1538
+ self.ctx.indent_level -= 2
1539
+
1540
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1541
+ if i == 0:
1542
+ out.write(f"{indent}{{\n")
1543
+ else:
1544
+ out.write(f"{indent}}} else {{\n")
1545
+ if isinstance(pattern, TpyCapturePattern):
1546
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, subject_expr, inner)
1547
+ self._emit_binding(out, as_name, as_raw, subject_expr, inner)
1548
+ self.ctx.indent_level += 2
1549
+ self._emit_case_body(out, case.body, case.type_facts)
1550
+ self.ctx.indent_level -= 2
1551
+
1552
+ elif isinstance(pattern, TpyOrPattern):
1553
+ or_conds: list[str] = []
1554
+ for alt in pattern.patterns:
1555
+ if isinstance(alt, TpyClassPattern):
1556
+ fc = self._record_field_conditions(alt, subject_expr)
1557
+ or_conds.append("(" + " && ".join(fc) + ")" if fc else "true")
1558
+ elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
1559
+ or_conds.clear()
1560
+ break
1561
+ else:
1562
+ raise CodeGenError(
1563
+ f"Unsupported or-pattern alt in Optional record: {type(alt).__name__}"
1564
+ )
1565
+ if or_conds:
1566
+ cond = " || ".join(or_conds)
1567
+ if guard is not None:
1568
+ guard_code = self.expressions.gen_expr(guard)
1569
+ self.ctx.temps.flush(out, indent)
1570
+ cond = f"({cond}) && {guard_code}"
1571
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1572
+ else:
1573
+ if i == 0:
1574
+ out.write(f"{indent}{{\n")
1575
+ else:
1576
+ out.write(f"{indent}}} else {{\n")
1577
+ self.ctx.indent_level += 2
1578
+ self._emit_case_body(out, case.body, case.type_facts)
1579
+ self.ctx.indent_level -= 2
1580
+
1581
+ else:
1582
+ raise CodeGenError(
1583
+ f"Unsupported pattern in Optional record dispatch: {type(pattern).__name__}"
1584
+ )
1585
+
1586
+ out.write(f"{indent}}}\n")
1587
+
1588
+ def _emit_optional_inner_if_elif(
1589
+ self, out: TextIO, cases: list['TpyMatchCase'], indent: str,
1590
+ ) -> None:
1591
+ """Emit if/elif chain for literal/value patterns on dereferenced Optional."""
1592
+ inner = INDENT * (self.ctx.indent_level + 2)
1593
+ deref = "__match_inner"
1594
+
1595
+ for i, case in enumerate(cases):
1596
+ self.ctx.emit_source_comment(out, case.loc, indent)
1597
+ keyword = "if" if i == 0 else "} else if"
1598
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1599
+ guard = case.guard
1600
+
1601
+ if isinstance(pattern, TpyLiteralPattern):
1602
+ cond = self._gen_literal_cond(pattern, deref)
1603
+ if guard is not None:
1604
+ guard_code = self.expressions.gen_expr(guard)
1605
+ self.ctx.temps.flush(out, indent)
1606
+ cond = f"{cond} && {guard_code}"
1607
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1608
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1609
+ self.ctx.indent_level += 2
1610
+ self._emit_case_body(out, case.body, case.type_facts)
1611
+ self.ctx.indent_level -= 2
1612
+
1613
+ elif isinstance(pattern, TpyValuePattern):
1614
+ val_code = self.expressions.gen_expr(pattern.expr)
1615
+ self.ctx.temps.flush(out, indent)
1616
+ cond = f"{deref} == {val_code}"
1617
+ if guard is not None:
1618
+ guard_code = self.expressions.gen_expr(guard)
1619
+ self.ctx.temps.flush(out, indent)
1620
+ cond = f"{cond} && {guard_code}"
1621
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1622
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1623
+ self.ctx.indent_level += 2
1624
+ self._emit_case_body(out, case.body, case.type_facts)
1625
+ self.ctx.indent_level -= 2
1626
+
1627
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1628
+ if i == 0:
1629
+ out.write(f"{indent}{{\n")
1630
+ else:
1631
+ out.write(f"{indent}}} else {{\n")
1632
+ if isinstance(pattern, TpyCapturePattern):
1633
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, deref, inner)
1634
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1635
+ self.ctx.indent_level += 2
1636
+ self._emit_case_body(out, case.body, case.type_facts)
1637
+ self.ctx.indent_level -= 2
1638
+
1639
+ elif isinstance(pattern, TpyOrPattern):
1640
+ or_conds: list[str] = []
1641
+ for alt in pattern.patterns:
1642
+ if isinstance(alt, TpyLiteralPattern):
1643
+ or_conds.append(self._gen_literal_cond(alt, deref))
1644
+ elif isinstance(alt, TpyValuePattern):
1645
+ val_code = self.expressions.gen_expr(alt.expr)
1646
+ self.ctx.temps.flush(out, indent)
1647
+ or_conds.append(f"{deref} == {val_code}")
1648
+ elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
1649
+ or_conds.clear()
1650
+ break
1651
+ else:
1652
+ raise CodeGenError(
1653
+ f"Unsupported or-pattern alt in Optional inner: {type(alt).__name__}"
1654
+ )
1655
+ if or_conds:
1656
+ cond = " || ".join(or_conds)
1657
+ if guard is not None:
1658
+ guard_code = self.expressions.gen_expr(guard)
1659
+ self.ctx.temps.flush(out, indent)
1660
+ cond = f"({cond}) && {guard_code}"
1661
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1662
+ else:
1663
+ if i == 0:
1664
+ out.write(f"{indent}{{\n")
1665
+ else:
1666
+ out.write(f"{indent}}} else {{\n")
1667
+ self.ctx.indent_level += 2
1668
+ self._emit_case_body(out, case.body, case.type_facts)
1669
+ self.ctx.indent_level -= 2
1670
+
1671
+ else:
1672
+ raise CodeGenError(
1673
+ f"Unsupported pattern in Optional inner dispatch: {type(pattern).__name__}"
1674
+ )
1675
+
1676
+ out.write(f"{indent}}}\n")
1677
+
1678
+ def _gen_literal_cond(
1679
+ self, pattern: 'TpyLiteralPattern', subject_expr: str = "__match_subject",
1680
+ ) -> str:
1681
+ """Generate a C++ comparison condition for a literal pattern."""
1682
+ val = pattern.value
1683
+ if isinstance(val, bool):
1684
+ return f"{subject_expr} == {'true' if val else 'false'}"
1685
+ elif isinstance(val, int):
1686
+ return f"{subject_expr} == {val}"
1687
+ elif isinstance(val, float):
1688
+ return f"{subject_expr} == {val!r}"
1689
+ elif isinstance(val, str):
1690
+ return f'{subject_expr} == {cpp_string_literal_expr(val)}'
1691
+ else:
1692
+ raise CodeGenError(f"Unsupported literal in match: {val!r}")
1693
+
1694
+ def _gen_match_if_elif_optional(
1695
+ self, out: TextIO, stmt: TpyMatch, subject_type: OptionalType, indent: str,
1696
+ ) -> None:
1697
+ """Generate match/case as if/elif chain for Optional subjects."""
1698
+ inner = INDENT * (self.ctx.indent_level + 1)
1699
+ uses_ptr = subject_type.uses_pointer_repr()
1700
+ # Determine how to check null and dereference
1701
+ null_cond = "__match_subject == nullptr" if uses_ptr else "!__match_subject.has_value()"
1702
+ has_val_cond = "__match_subject != nullptr" if uses_ptr else "__match_subject.has_value()"
1703
+ deref = "(*__match_subject)"
1704
+
1705
+ for i, case in enumerate(stmt.cases):
1706
+ self.ctx.emit_source_comment(out, case.loc, indent)
1707
+ keyword = "if" if i == 0 else "} else if"
1708
+ pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
1709
+ guard = case.guard
1710
+
1711
+ if isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
1712
+ # case None:
1713
+ cond = null_cond
1714
+ if guard is not None:
1715
+ guard_code = self.expressions.gen_expr(guard)
1716
+ self.ctx.temps.flush(out, indent)
1717
+ cond = f"{cond} && {guard_code}"
1718
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1719
+ self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
1720
+ self.ctx.indent_level += 1
1721
+ self._emit_case_body(out, case.body, case.type_facts)
1722
+ self.ctx.indent_level -= 1
1723
+
1724
+ elif isinstance(pattern, TpyLiteralPattern):
1725
+ # Literal match on inner value (e.g. case 42: on Optional[Int32])
1726
+ lit_cond = self._gen_literal_cond(pattern, deref)
1727
+ cond = f"{has_val_cond} && {lit_cond}"
1728
+ if guard is not None:
1729
+ guard_code = self.expressions.gen_expr(guard)
1730
+ self.ctx.temps.flush(out, indent)
1731
+ cond = f"{cond} && {guard_code}"
1732
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1733
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1734
+ self.ctx.indent_level += 1
1735
+ self._emit_case_body(out, case.body, case.type_facts)
1736
+ self.ctx.indent_level -= 1
1737
+
1738
+ elif isinstance(pattern, TpyValuePattern):
1739
+ # Value pattern on inner type (e.g. case Color.RED: on Optional[Color])
1740
+ val_code = self.expressions.gen_expr(pattern.expr)
1741
+ self.ctx.temps.flush(out, indent)
1742
+ cond = f"{has_val_cond} && {deref} == {val_code}"
1743
+ if guard is not None:
1744
+ guard_code = self.expressions.gen_expr(guard)
1745
+ self.ctx.temps.flush(out, indent)
1746
+ cond = f"{cond} && {guard_code}"
1747
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1748
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1749
+ self.ctx.indent_level += 1
1750
+ self._emit_case_body(out, case.body, case.type_facts)
1751
+ self.ctx.indent_level -= 1
1752
+
1753
+ elif isinstance(pattern, TpyClassPattern):
1754
+ # Class pattern on inner record type (e.g. case Point(x=0): on Optional[Point])
1755
+ field_conds = self._record_field_conditions(pattern, deref)
1756
+ cond_parts = [has_val_cond] + field_conds
1757
+ cond = " && ".join(cond_parts)
1758
+ if guard is not None:
1759
+ guard_code = self.expressions.gen_expr(guard)
1760
+ self.ctx.temps.flush(out, indent)
1761
+ cond = f"{cond} && {guard_code}"
1762
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1763
+ # Emit field bindings using dereferenced subject
1764
+ self._gen_match_field_bindings(out, pattern, deref, inner)
1765
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1766
+ self.ctx.indent_level += 1
1767
+ self._emit_case_body(out, case.body, case.type_facts)
1768
+ self.ctx.indent_level -= 1
1769
+
1770
+ elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
1771
+ needs_deref = (
1772
+ isinstance(pattern, TpyCapturePattern) or as_name is not None
1773
+ )
1774
+ if isinstance(pattern, TpyCapturePattern) and guard is not None:
1775
+ # Guarded capture: condition on has_value + guard
1776
+ cond = f"{has_val_cond} && {self.expressions.gen_expr(guard)}"
1777
+ self.ctx.temps.flush(out, indent)
1778
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1779
+ elif guard is not None:
1780
+ # Guarded wildcard (no capture)
1781
+ guard_code = self.expressions.gen_expr(guard)
1782
+ self.ctx.temps.flush(out, indent)
1783
+ out.write(f"{indent}{keyword} ({guard_code}) {{\n")
1784
+ elif needs_deref:
1785
+ # Unguarded capture: guard on has_value to avoid UB
1786
+ out.write(f"{indent}{keyword} ({has_val_cond}) {{\n")
1787
+ elif i == 0:
1788
+ out.write(f"{indent}{{\n")
1789
+ else:
1790
+ out.write(f"{indent}}} else {{\n")
1791
+ if isinstance(pattern, TpyCapturePattern):
1792
+ self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, deref, inner)
1793
+ self._emit_binding(out, as_name, as_raw, deref, inner)
1794
+ self.ctx.indent_level += 1
1795
+ self._emit_case_body(out, case.body, case.type_facts)
1796
+ self.ctx.indent_level -= 1
1797
+
1798
+ elif isinstance(pattern, TpyOrPattern):
1799
+ # OR of None/literal/value/class conditions
1800
+ or_conds: list[str] = []
1801
+ for alt in pattern.patterns:
1802
+ if isinstance(alt, TpyLiteralPattern) and alt.value is None:
1803
+ or_conds.append(null_cond)
1804
+ elif isinstance(alt, TpyLiteralPattern):
1805
+ lit_c = self._gen_literal_cond(alt, deref)
1806
+ or_conds.append(f"({has_val_cond} && {lit_c})")
1807
+ elif isinstance(alt, TpyValuePattern):
1808
+ val_code = self.expressions.gen_expr(alt.expr)
1809
+ self.ctx.temps.flush(out, indent)
1810
+ or_conds.append(f"({has_val_cond} && {deref} == {val_code})")
1811
+ elif isinstance(alt, TpyClassPattern):
1812
+ fc = self._record_field_conditions(alt, deref)
1813
+ parts = [has_val_cond] + fc
1814
+ or_conds.append("(" + " && ".join(parts) + ")")
1815
+ elif isinstance(alt, TpyWildcardPattern):
1816
+ or_conds.clear()
1817
+ break
1818
+ else:
1819
+ raise CodeGenError(
1820
+ f"Unsupported or-pattern alt for Optional: {type(alt).__name__}"
1821
+ )
1822
+ if or_conds:
1823
+ cond = " || ".join(or_conds)
1824
+ if guard is not None:
1825
+ guard_code = self.expressions.gen_expr(guard)
1826
+ self.ctx.temps.flush(out, indent)
1827
+ cond = f"({cond}) && {guard_code}"
1828
+ out.write(f"{indent}{keyword} ({cond}) {{\n")
1829
+ else:
1830
+ if guard is not None:
1831
+ guard_code = self.expressions.gen_expr(guard)
1832
+ self.ctx.temps.flush(out, indent)
1833
+ out.write(f"{indent}{keyword} ({guard_code}) {{\n")
1834
+ elif i == 0:
1835
+ out.write(f"{indent}{{\n")
1836
+ else:
1837
+ out.write(f"{indent}}} else {{\n")
1838
+ self.ctx.indent_level += 1
1839
+ self._emit_case_body(out, case.body, case.type_facts)
1840
+ self.ctx.indent_level -= 1
1841
+
1842
+ else:
1843
+ raise CodeGenError(
1844
+ f"Unsupported match pattern for Optional: {type(pattern).__name__}"
1845
+ )
1846
+
1847
+ out.write(f"{indent}}}\n")
1848
+
1849
+ def _record_field_conditions(
1850
+ self, pattern: 'TpyClassPattern', subject_expr: str = "__match_subject",
1851
+ ) -> list[str]:
1852
+ """Generate C++ field comparison conditions for a record class pattern."""
1853
+ conds: list[str] = []
1854
+ for field_name, sub_pattern in pattern.keywords:
1855
+ inner = sub_pattern
1856
+ if isinstance(inner, TpyAsPattern):
1857
+ inner = inner.pattern
1858
+ if isinstance(inner, TpyLiteralPattern):
1859
+ val = inner.value
1860
+ if isinstance(val, bool):
1861
+ conds.append(f"{subject_expr}.{field_name} == {'true' if val else 'false'}")
1862
+ elif isinstance(val, int):
1863
+ conds.append(f"{subject_expr}.{field_name} == {val}")
1864
+ elif isinstance(val, float):
1865
+ conds.append(f"{subject_expr}.{field_name} == {val!r}")
1866
+ elif isinstance(val, str):
1867
+ conds.append(f'{subject_expr}.{field_name} == {cpp_string_literal_expr(val)}')
1868
+ elif isinstance(inner, TpyClassPattern) and inner.is_union_field_guard:
1869
+ # Union-typed field: runtime holds_alternative check
1870
+ assert inner.resolved_type is not None
1871
+ cpp_type = self.types.type_to_cpp(inner.resolved_type)
1872
+ conds.append(f"std::holds_alternative<{cpp_type}>({subject_expr}.{field_name})")
1873
+ # Recurse for nested field conditions on the variant member
1874
+ if inner.keywords:
1875
+ get_expr = f"std::get<{cpp_type}>({subject_expr}.{field_name})"
1876
+ nested = self._record_field_conditions(inner, get_expr)
1877
+ conds.extend(nested)
1878
+ elif isinstance(inner, TpyClassPattern) and not inner.is_union_field_guard and inner.keywords:
1879
+ # Non-union record field with nested sub-patterns: recurse through it
1880
+ nested = self._record_field_conditions(inner, f"{subject_expr}.{field_name}")
1881
+ conds.extend(nested)
1882
+ return conds
1883
+
1884
+ @staticmethod
1885
+ def _sub_has_union_field_guard(sub: TpyPattern) -> bool:
1886
+ """Check if a field sub-pattern contains a union field guard."""
1887
+ inner = sub
1888
+ if isinstance(inner, TpyAsPattern):
1889
+ inner = inner.pattern
1890
+ return isinstance(inner, TpyClassPattern) and inner.is_union_field_guard
1891
+
1892
+ def _has_shared_variant_index(self, stmt: TpyMatch, subject_type: UnionType) -> bool:
1893
+ """Check if multiple cases resolve to the same variant index (e.g. union field guards)."""
1894
+ seen: set[int] = set()
1895
+ for case in stmt.cases:
1896
+ pat = case.pattern
1897
+ if isinstance(pat, TpyAsPattern):
1898
+ pat = pat.pattern
1899
+ indices: list[int] = []
1900
+ if isinstance(pat, TpyClassPattern) and pat.resolved_type is not None:
1901
+ indices.append(self._variant_index(subject_type, pat.resolved_type))
1902
+ elif isinstance(pat, TpyOrPattern):
1903
+ for alt in pat.patterns:
1904
+ if isinstance(alt, TpyClassPattern) and alt.resolved_type is not None:
1905
+ indices.append(self._variant_index(subject_type, alt.resolved_type))
1906
+ for idx in indices:
1907
+ if idx in seen:
1908
+ return True
1909
+ seen.add(idx)
1910
+ return False
1911
+
1912
+ def _variant_index(self, union_type: UnionType, member_type: TpyType) -> int:
1913
+ """Find the index of a member type in a union's canonical member ordering.
1914
+
1915
+ For recursive unions (including narrowed-by-None subsets), indices are
1916
+ resolved against the alias's full member tuple so they match the
1917
+ wrapper struct's variant ordering.
1918
+ """
1919
+ wrapper = union_type.wrapper_info()
1920
+ members = wrapper.full_members if wrapper is not None else union_type.members
1921
+ for i, m in enumerate(members):
1922
+ if m == member_type:
1923
+ return i
1924
+ raise CodeGenError(f"type '{member_type}' not found in union '{union_type}'")
1925
+
1926
+ def _switch_literal_label(self, pattern: TpyLiteralPattern) -> str:
1927
+ """Generate a C++ case label for a literal pattern (int or bool)."""
1928
+ val = pattern.value
1929
+ if isinstance(val, bool):
1930
+ return "true" if val else "false"
1931
+ elif isinstance(val, int):
1932
+ return str(val)
1933
+ else:
1934
+ raise CodeGenError(f"Cannot use literal {val!r} in switch case label")
1935
+
1936
+ def _record_capture_type(
1937
+ self, pattern: TpyClassPattern, field_name: str, capture_name: str,
1938
+ ) -> None:
1939
+ """Register the field's declared type for a match-bound capture so
1940
+ ``_get_cpp_declared_type`` (which only looks at ``var_types``) can
1941
+ drive narrowing-aware deref on the captured name."""
1942
+ record_type = pattern.resolved_type
1943
+ if not isinstance(record_type, NominalType):
1944
+ return
1945
+ record = self.ctx.analyzer.registry.get_record_for_type(record_type)
1946
+ for f in record.fields:
1947
+ if f.name == field_name:
1948
+ self.ctx.var_types[capture_name] = f.type
1949
+ return
1950
+
1951
+ def _gen_match_field_bindings(
1952
+ self, out: TextIO, pattern: TpyClassPattern, case_var: str, indent: str,
1953
+ ) -> None:
1954
+ """Emit local variable bindings for class pattern keyword fields."""
1955
+ for field_name, sub_pattern in pattern.keywords:
1956
+ if isinstance(sub_pattern, TpyCapturePattern):
1957
+ self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
1958
+ self._record_capture_type(pattern, field_name, sub_pattern.name)
1959
+ elif isinstance(sub_pattern, TpyWildcardPattern):
1960
+ pass
1961
+ elif isinstance(sub_pattern, TpyLiteralPattern):
1962
+ pass # Literal sub-patterns handled as conditions (future)
1963
+ elif isinstance(sub_pattern, TpyClassPattern):
1964
+ if sub_pattern.is_union_field_guard and sub_pattern.keywords:
1965
+ # Union field with nested record patterns: extract variant, bind sub-fields
1966
+ cpp_type = self.types.type_to_cpp(sub_pattern.resolved_type)
1967
+ # Include parent var in temp name to avoid collisions when
1968
+ # sibling fields share a sub-field name
1969
+ parent_sfx = case_var.replace(".", "_").replace("*", "").lstrip("_")
1970
+ temp = f"__field_{parent_sfx}_{field_name}"
1971
+ out.write(f"{indent}auto& {temp} = std::get<{cpp_type}>({case_var}.{field_name});\n")
1972
+ self._gen_match_field_bindings(out, sub_pattern, temp, indent)
1973
+ elif not sub_pattern.is_union_field_guard and sub_pattern.keywords:
1974
+ # Compile-time type guard with nested field bindings: recurse with direct access
1975
+ self._gen_match_field_bindings(out, sub_pattern, f"{case_var}.{field_name}", indent)
1976
+ # else: type guard only (no nested bindings), no output needed
1977
+ elif isinstance(sub_pattern, TpyAsPattern):
1978
+ inner_sub = sub_pattern.pattern
1979
+ if isinstance(inner_sub, TpyClassPattern):
1980
+ # Type pattern with capture (e.g., value=str() as v)
1981
+ if inner_sub.is_union_field_guard:
1982
+ cpp_type = self.types.type_to_cpp(inner_sub.resolved_type)
1983
+ field_expr = f"std::get<{cpp_type}>({case_var}.{field_name})"
1984
+ else:
1985
+ field_expr = f"{case_var}.{field_name}"
1986
+ self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, field_expr, indent)
1987
+ # Recurse for nested field bindings
1988
+ if inner_sub.keywords:
1989
+ self._gen_match_field_bindings(out, inner_sub, escape_cpp_name(sub_pattern.name), indent)
1990
+ elif isinstance(inner_sub, TpyLiteralPattern):
1991
+ self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
1992
+ elif isinstance(inner_sub, (TpyWildcardPattern, TpyCapturePattern)):
1993
+ if isinstance(inner_sub, TpyCapturePattern):
1994
+ self._emit_binding(out, escape_cpp_name(inner_sub.name), inner_sub.name, f"{case_var}.{field_name}", indent)
1995
+ self._record_capture_type(pattern, field_name, inner_sub.name)
1996
+ self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
1997
+ self._record_capture_type(pattern, field_name, sub_pattern.name)