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
tpyc/sema/match.py ADDED
@@ -0,0 +1,1177 @@
1
+ """
2
+ TurboPython Match/Case Semantic Analysis
3
+
4
+ Semantic analysis for match/case statements and pattern matching.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from enum import Enum
9
+ from typing import TYPE_CHECKING
10
+
11
+ from ..typesys import (
12
+ TpyType,
13
+ NominalType, AliasRef, RecursiveAliasInstanceType,
14
+ NoneType, OptionalType, UnionType, PendingStrType,
15
+ LiteralType, LiteralValue, LiteralTag, TypeParamRef,
16
+ unwrap_readonly, unwrap_ref_type, make_union,
17
+ is_float_type, is_any_str_type,
18
+ )
19
+ from ..modules import _resolve_concrete_type_name
20
+ from .flow_facts import FlowFacts
21
+ from ..type_def_registry import (
22
+ is_bool_type, is_fixed_int_type, is_big_int_type,
23
+ is_str_category, is_char_type, is_float_category,
24
+ is_enum_type, enum_info_of,
25
+ )
26
+ from ..parse import (
27
+ TpyName, TpyFieldAccess,
28
+ TpyMatch, TpyMatchCase, TpyPattern, TpyWildcardPattern, TpyCapturePattern,
29
+ TpyClassPattern, TpyLiteralPattern, TpyValuePattern, TpyOrPattern, TpyAsPattern,
30
+ )
31
+ from ..parse.nodes import stmt_has_any_suspension
32
+
33
+ if TYPE_CHECKING:
34
+ from ..typesys import RecordInfo
35
+ from .context import SemanticContext
36
+ from .expressions import ExpressionAnalyzer
37
+ from .statements import StatementAnalyzer
38
+
39
+
40
+ def _subst_type_params(typ: TpyType, subst: dict[str, TpyType]) -> TpyType:
41
+ """Substitute TypeParamRef instances in a type according to subst map."""
42
+ if isinstance(typ, TypeParamRef) and typ.name in subst:
43
+ return subst[typ.name]
44
+ return typ.map_inner_types(lambda t: _subst_type_params(t, subst))
45
+
46
+
47
+ class OrPatternKind(Enum):
48
+ UNION = "union"
49
+ NONUNION = "nonunion"
50
+ RECORD = "record"
51
+ OPTIONAL = "optional"
52
+
53
+
54
+ class MatchAnalyzer:
55
+ """Semantic analysis for match/case statements."""
56
+
57
+ def __init__(self, ctx: SemanticContext, stmts: StatementAnalyzer,
58
+ expr: ExpressionAnalyzer):
59
+ self.ctx = ctx
60
+ self.stmts = stmts
61
+ self.expr = expr
62
+
63
+ def analyze_match(self, stmt: TpyMatch) -> None:
64
+ """Analyze a match/case statement."""
65
+ subject_type = self.expr.analyze_expr(stmt.subject)
66
+ effective_type = unwrap_ref_type(unwrap_readonly(subject_type))
67
+ # Expand recursive union alias placeholder to its underlying
68
+ # UnionType so match dispatch sees the variant arms.
69
+ if isinstance(effective_type, AliasRef):
70
+ alias = self.ctx.registry.get_type_alias(effective_type.name)
71
+ if alias is not None:
72
+ effective_type = alias
73
+ stmt.subject_type = effective_type
74
+ # A generic recursive alias instance (Tree[int]) stays the codegen
75
+ # subject -- its wrapper_info() drives .value variant dispatch. For the
76
+ # (UnionType-centric) pattern arm-analysis below, stand in a synthesized
77
+ # union of its substituted alternatives so the existing union path
78
+ # applies unchanged.
79
+ if isinstance(effective_type, RecursiveAliasInstanceType):
80
+ effective_type = make_union(*effective_type.alternatives())
81
+ is_union = isinstance(effective_type, UnionType)
82
+ is_enum = is_enum_type(effective_type)
83
+ is_literal = isinstance(effective_type, LiteralType)
84
+ is_primitive = is_literal or (
85
+ is_fixed_int_type(effective_type) or is_big_int_type(effective_type)
86
+ or is_float_category(effective_type) or is_bool_type(effective_type)
87
+ or is_str_category(effective_type) or is_char_type(effective_type)
88
+ or isinstance(effective_type, PendingStrType)
89
+ )
90
+ is_record = (
91
+ isinstance(effective_type, NominalType)
92
+ and effective_type.is_user_record
93
+ and self.ctx.registry.get_record(effective_type.name) is not None
94
+ )
95
+ is_optional = isinstance(effective_type, OptionalType)
96
+ if not (is_union or is_enum or is_primitive or is_record or is_optional):
97
+ raise self.ctx.error(
98
+ f"match subject must be a union, enum, primitive, record, "
99
+ f"or Optional type, got '{effective_type}'", stmt
100
+ )
101
+
102
+ # Subject variable name for narrowing (only if simple name)
103
+ subject_name: str | None = None
104
+ if isinstance(stmt.subject, TpyName):
105
+ subject_name = stmt.subject.name
106
+
107
+ # Resumable frame (H1): when a generator/async `match` carries a
108
+ # suspension, its arm bodies become separate states, so pattern
109
+ # bindings must be frame fields (see the per-arm binding loop).
110
+ # `current_function` may be a module-init sentinel for a top-level
111
+ # `match` (no `is_generator`/`is_async`); a suspension cannot appear
112
+ # at module scope anyway, so guard with getattr.
113
+ cur_fn = self.ctx.func.current_function
114
+ needs_frame_field = (
115
+ (getattr(cur_fn, "is_generator", False)
116
+ or getattr(cur_fn, "is_async", False))
117
+ and stmt_has_any_suspension(stmt)
118
+ )
119
+
120
+ had_wildcard = False
121
+ seen_types: set[str] = set()
122
+ seen_values: set[object] = set()
123
+
124
+ scope_before = set(self.ctx.func.current_scope.bindings.keys())
125
+ assigned_before = frozenset(self.ctx.func.definitely_assigned)
126
+ bindings_before = dict(self.ctx.func.current_scope.bindings)
127
+ ns_types_before = self.stmts._save_ns_var_types()
128
+ before = self.stmts.init.save()
129
+
130
+ arm_states: list[FlowFacts] = []
131
+ arm_bindings: list[dict[str, TpyType]] = []
132
+ consumed_before = self.ctx.func.current_consumed_own_params.copy()
133
+ arm_consumed: list[tuple[set[str], bool]] = [] # (consumed_set, terminated)
134
+
135
+ for case in stmt.cases:
136
+ if had_wildcard:
137
+ raise self.ctx.error(
138
+ "unreachable case after wildcard pattern", case.pattern
139
+ )
140
+ # Restore state to pre-match for each arm
141
+ self.stmts.init.restore(before)
142
+ self.ctx.func.current_consumed_own_params = consumed_before.copy()
143
+ self.ctx.func.current_scope.bindings = dict(bindings_before)
144
+ self.stmts._restore_ns_var_types(ns_types_before)
145
+
146
+ pattern_bindings: dict[str, TpyType] = {}
147
+ # Guarded cases don't consume types/values for duplicate detection,
148
+ # because the guard may fail and fall through.
149
+ has_guard = case.guard is not None
150
+ saved_seen_types = set(seen_types) if has_guard else None
151
+ saved_seen_values = set(seen_values) if has_guard else None
152
+ if is_union:
153
+ self._analyze_pattern(case.pattern, effective_type, seen_types, pattern_bindings, stmt)
154
+ elif is_record:
155
+ self._analyze_pattern_record(
156
+ case.pattern, effective_type, pattern_bindings, stmt,
157
+ )
158
+ elif is_optional:
159
+ self._analyze_pattern_optional(
160
+ case.pattern, effective_type, seen_values, pattern_bindings, stmt,
161
+ )
162
+ else:
163
+ self._analyze_pattern_nonunion(
164
+ case.pattern, effective_type, seen_values, pattern_bindings, stmt,
165
+ )
166
+ if has_guard:
167
+ seen_types.clear()
168
+ seen_types.update(saved_seen_types) # type: ignore[arg-type]
169
+ seen_values.clear()
170
+ seen_values.update(saved_seen_values) # type: ignore[arg-type]
171
+
172
+ for name, ty in pattern_bindings.items():
173
+ self.ctx.func.current_scope.define(name, ty)
174
+ self.stmts.init.mark_assigned(name)
175
+ if name not in self.ctx.func.var_scope_depth:
176
+ self.ctx.func.var_scope_depth[name] = self.ctx.func.current_scope.depth
177
+ # Resumable frame (H1): a `match` carrying a suspension
178
+ # decomposes into per-arm body states, so a pattern binding
179
+ # read in an arm body must live in the frame rather than as a
180
+ # dispatch-local that vanishes at the state split. Register it
181
+ # in the function namespace so `_analyze_function` collects it
182
+ # into `generator_locals` (-> a struct field). Pattern
183
+ # bindings otherwise only land in `current_scope`, which the
184
+ # generator-local collection does not read.
185
+ if needs_frame_field and self.ctx.func.current_ns is not None:
186
+ self.ctx.func.current_ns.bind_variable(name, ty)
187
+
188
+ # Narrow subject variable for class patterns (union only)
189
+ narrowing_facts = self._match_case_narrowing_facts(
190
+ case.pattern, subject_name, effective_type,
191
+ ) if is_union else {}
192
+ if narrowing_facts:
193
+ case.type_facts = self.stmts._filter_union_codegen_facts(narrowing_facts)
194
+ self.ctx.func.narrowed_types.update(narrowing_facts)
195
+
196
+ # Narrow Optional subject to inner type in non-None arms
197
+ if is_optional and subject_name is not None:
198
+ pat = case.pattern
199
+ if isinstance(pat, TpyAsPattern):
200
+ pat = pat.pattern
201
+ is_none_arm = isinstance(pat, TpyLiteralPattern) and pat.value is None
202
+ if not is_none_arm:
203
+ self.ctx.func.narrowed_types[subject_name] = effective_type.inner
204
+
205
+ # Narrow Literal subject to matched value(s)
206
+ if is_literal and subject_name is not None:
207
+ matched = self._extract_literal_pattern_values(case.pattern, effective_type)
208
+ if matched is not None:
209
+ narrowed = LiteralType(effective_type.base_type, tuple(matched))
210
+ self.ctx.func.narrowed_types[subject_name] = narrowed
211
+ facts = {subject_name: narrowed}
212
+ case.type_facts = self.stmts._filter_union_codegen_facts(facts)
213
+
214
+ # Analyze guard expression (pattern bindings are in scope)
215
+ if case.guard is not None:
216
+ self.expr.analyze_expr(case.guard)
217
+
218
+ for s in case.body:
219
+ self.stmts.analyze_stmt(s)
220
+
221
+ arm_states.append(self.stmts.init.save())
222
+ arm_consumed.append((self.ctx.func.current_consumed_own_params.copy(), self.ctx.func.init_terminated))
223
+ arm_bindings.append(dict(self.ctx.func.current_scope.bindings))
224
+
225
+ pat = case.pattern
226
+ if isinstance(pat, TpyAsPattern):
227
+ pat = pat.pattern
228
+ if isinstance(pat, (TpyWildcardPattern, TpyCapturePattern)) and case.guard is None:
229
+ had_wildcard = True
230
+ # Class pattern on concrete record with no conditions is always-matching
231
+ elif ((is_record or is_optional) and isinstance(pat, TpyClassPattern)
232
+ and case.guard is None
233
+ and not any(self._is_constraining_sub_pattern(sub)
234
+ for _, sub in pat.keywords)):
235
+ had_wildcard = True
236
+
237
+ # Exhaustiveness check for finite-valued types
238
+ missing = (
239
+ [] if had_wildcard
240
+ else self._match_missing_cases(effective_type, seen_types, seen_values)
241
+ )
242
+ stmt.is_exhaustive = not missing
243
+ if missing:
244
+ if missing == [None]:
245
+ msg = (
246
+ f"non-exhaustive match on '{effective_type}'; "
247
+ f"no unconditional catch-all arm "
248
+ f"(add 'case _:' to suppress)"
249
+ )
250
+ else:
251
+ msg = (
252
+ f"non-exhaustive match on '{effective_type}'; "
253
+ f"missing: {', '.join(missing)} "
254
+ f"(add 'case _:' to suppress)"
255
+ )
256
+ self.ctx.warning(msg, stmt)
257
+
258
+ # Merge flow states across all arms
259
+ self._merge_match_arms(arm_states, before)
260
+
261
+ # Merge consumed Own[T] params: intersect non-terminated arms.
262
+ # Terminated arms don't affect live continuation (same as if/else).
263
+ if arm_consumed:
264
+ live_sets = [s for s, terminated in arm_consumed if not terminated]
265
+ dead_sets = [s for s, terminated in arm_consumed if terminated]
266
+ if live_sets:
267
+ merged_consumed = live_sets[0]
268
+ for s in live_sets[1:]:
269
+ merged_consumed = merged_consumed & s
270
+ self.ctx.func.current_consumed_own_params = merged_consumed
271
+ elif dead_sets:
272
+ self.ctx.func.current_consumed_own_params = set().union(*dead_sets)
273
+ else:
274
+ self.ctx.func.current_consumed_own_params = consumed_before
275
+
276
+ # Restore scope bindings, merging types from arms
277
+ self.ctx.func.current_scope.bindings = dict(bindings_before)
278
+ self.stmts._restore_ns_var_types(ns_types_before)
279
+ for arm_b in arm_bindings:
280
+ for name, ty in arm_b.items():
281
+ if name not in bindings_before:
282
+ self.ctx.func.current_scope.define(name, ty)
283
+
284
+ self.stmts._sync_promoted_var_types(
285
+ set().union(*(set(b) for b in arm_bindings))
286
+ )
287
+
288
+ # Pre-declare variables first declared inside match arms
289
+ if not self.ctx.func.init_terminated:
290
+ branch_new = set(self.ctx.func.current_scope.bindings.keys()) - scope_before
291
+ newly_assigned = self.ctx.func.definitely_assigned - assigned_before
292
+ predecl = (branch_new & newly_assigned) - self.ctx.func.global_declarations
293
+ else:
294
+ predecl = set()
295
+ if predecl:
296
+ self.ctx.if_branch_decls[id(stmt)] = {
297
+ name: self.ctx.func.current_scope.lookup(name)
298
+ for name in sorted(predecl)
299
+ }
300
+
301
+ def _match_case_narrowing_facts(
302
+ self, pattern: TpyPattern, subject_name: str | None,
303
+ subject_type: TpyType,
304
+ ) -> dict[str, TpyType]:
305
+ """Compute narrowing facts for a match case pattern."""
306
+ if subject_name is None:
307
+ return {}
308
+ # Unwrap as-pattern to get inner
309
+ inner = pattern
310
+ if isinstance(inner, TpyAsPattern):
311
+ inner = inner.pattern
312
+ if isinstance(inner, TpyClassPattern) and inner.resolved_type is not None:
313
+ return {subject_name: inner.resolved_type}
314
+ return {}
315
+
316
+ def _merge_match_arms(
317
+ self, arm_states: list['FlowFacts'], before: 'FlowFacts',
318
+ ) -> None:
319
+ """Merge flow states from multiple match arms.
320
+
321
+ Uses the same logic as merge_branches: intersect definitely_assigned
322
+ across non-terminated arms, union across terminated arms.
323
+ """
324
+ if not arm_states:
325
+ self.stmts.init.restore(before)
326
+ return
327
+ if len(arm_states) == 1:
328
+ self.stmts.init.restore(arm_states[0])
329
+ return
330
+ # Pairwise merge: merge first two, then merge result with next, etc.
331
+ self.stmts.init.restore(arm_states[0])
332
+ for i in range(1, len(arm_states)):
333
+ current = self.stmts.init.save()
334
+ self.stmts.init.restore(before)
335
+ self.stmts.init.merge_branches(current, arm_states[i])
336
+
337
+ def _match_missing_cases(
338
+ self, subject_type: TpyType,
339
+ seen_types: set[str], seen_values: set[object],
340
+ ) -> list[str]:
341
+ """Return human-readable names of uncovered cases for finite-valued types."""
342
+ if isinstance(subject_type, UnionType):
343
+ return [
344
+ "None" if isinstance(m, NoneType) else str(m)
345
+ for m in subject_type.members
346
+ if str(m) not in seen_types
347
+ ]
348
+
349
+ if is_enum_type(subject_type):
350
+ einfo = enum_info_of(subject_type)
351
+ return [
352
+ f"{subject_type.name}.{name}"
353
+ for name in einfo.members
354
+ if (subject_type.name, name) not in seen_values
355
+ ]
356
+
357
+ if isinstance(subject_type, OptionalType):
358
+ if None not in seen_values:
359
+ return ["None"]
360
+ return []
361
+
362
+ if is_bool_type(subject_type):
363
+ missing: list[str] = []
364
+ if True not in seen_values:
365
+ missing.append("True")
366
+ if False not in seen_values:
367
+ missing.append("False")
368
+ return missing
369
+
370
+ if isinstance(subject_type, LiteralType):
371
+ all_values = {v.value for v in subject_type.values}
372
+ missing = sorted(
373
+ (str(v) if not isinstance(v, str) else f'"{v}"')
374
+ for v in all_values if v not in seen_values
375
+ )
376
+ return missing
377
+
378
+ if isinstance(subject_type, NominalType) and subject_type.is_user_record:
379
+ return [None] # type: ignore[list-item] # sentinel: no enumerable missing cases
380
+
381
+ return []
382
+
383
+ def _analyze_pattern(
384
+ self, pattern: TpyPattern, subject_type: UnionType,
385
+ seen_types: set[str], bindings: dict[str, TpyType], stmt: TpyMatch,
386
+ ) -> None:
387
+ """Analyze a pattern against the subject type and collect bindings."""
388
+ if isinstance(pattern, TpyWildcardPattern):
389
+ return
390
+
391
+ elif isinstance(pattern, TpyCapturePattern):
392
+ bindings[pattern.name] = subject_type
393
+
394
+ elif isinstance(pattern, TpyAsPattern):
395
+ # `case None as x:` has no value to bind (NoneType is monostate);
396
+ # reject rather than silently dropping the binding.
397
+ if (isinstance(pattern.pattern, TpyLiteralPattern)
398
+ and pattern.pattern.value is None):
399
+ raise self.ctx.error(
400
+ "'as' binding not allowed on 'case None:'", pattern,
401
+ )
402
+ self._analyze_pattern(pattern.pattern, subject_type, seen_types, bindings, stmt)
403
+ # Bind as-variable to narrowed type when inner pattern is a class
404
+ if isinstance(pattern.pattern, TpyClassPattern) and pattern.pattern.resolved_type is not None:
405
+ bindings[pattern.name] = pattern.pattern.resolved_type
406
+ else:
407
+ bindings[pattern.name] = subject_type
408
+
409
+ elif isinstance(pattern, TpyClassPattern):
410
+ self._analyze_class_pattern(pattern, subject_type, seen_types, bindings, stmt)
411
+
412
+ elif isinstance(pattern, TpyOrPattern):
413
+ self._analyze_or_pattern(pattern, subject_type, seen_types, bindings, stmt, kind=OrPatternKind.UNION)
414
+
415
+ elif isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
416
+ if not subject_type.has_none_member():
417
+ raise self.ctx.error(
418
+ f"'case None:' requires None to be a member of the "
419
+ f"union subject; got '{subject_type}'", pattern,
420
+ )
421
+ none_key = str(NoneType())
422
+ if none_key in seen_types:
423
+ raise self.ctx.error(
424
+ "duplicate 'case None:' arm", pattern,
425
+ )
426
+ seen_types.add(none_key)
427
+ pattern.resolved_type = NoneType()
428
+
429
+ else:
430
+ raise self.ctx.error(
431
+ f"Unsupported pattern type in match on union: "
432
+ f"{type(pattern).__name__}", pattern
433
+ )
434
+
435
+ def _analyze_class_pattern(
436
+ self, pattern: TpyClassPattern, subject_type: UnionType,
437
+ seen_types: set[str], bindings: dict[str, TpyType], stmt: TpyMatch,
438
+ ) -> None:
439
+ """Analyze a class pattern: validate union membership and field bindings."""
440
+ if not isinstance(pattern.cls, TpyName):
441
+ raise self.ctx.error(
442
+ "class pattern must use a simple name", pattern
443
+ )
444
+ cls_name = pattern.cls.name
445
+
446
+ # Resolve to a type and find matching union member.
447
+ # Try exact match first (records, primitives), then fall back to
448
+ # searching union members by base name (handles parameterized types
449
+ # like list[Tree], Box[str] matched by bare list(), Box()).
450
+ record = self.ctx.registry.get_record(cls_name)
451
+ resolved_type = self._resolve_pattern_type(
452
+ cls_name, subject_type, record is not None, pattern)
453
+
454
+ pattern.resolved_type = resolved_type
455
+
456
+ if record is not None:
457
+ self._resolve_class_pattern_fields(pattern, record, bindings)
458
+ elif pattern.keywords:
459
+ raise self.ctx.error(
460
+ f"type '{cls_name}' does not support field patterns", pattern
461
+ )
462
+
463
+ # Build type key for duplicate detection.
464
+ # Include union field guards so that e.g. Box(value=Cat()) and
465
+ # Box(value=Dog()) on Box[Cat|Dog] are distinct cases.
466
+ type_key = self._build_pattern_type_key(pattern, resolved_type)
467
+ if type_key in seen_types:
468
+ raise self.ctx.error(
469
+ f"duplicate case for '{cls_name}' in match statement", pattern
470
+ )
471
+ seen_types.add(type_key)
472
+
473
+ def _resolve_pattern_type(
474
+ self, name: str, subject_type: UnionType,
475
+ is_record: bool, pattern: TpyClassPattern,
476
+ ) -> TpyType:
477
+ """Resolve a type name in a match class pattern against a union subject.
478
+
479
+ Resolution order:
480
+ 1. Exact match: record NominalType or primitive (Int32, str, bool, ...)
481
+ 2. Name-based member search: find the union member whose base name
482
+ matches (handles parameterized types like list[T], Box[str])
483
+ """
484
+ resolved = _resolve_concrete_type_name(name)
485
+ if resolved is None and is_record:
486
+ # Mint qname so exact-match against union members (line 441
487
+ # below) works under strict NominalType equality. Without
488
+ # the qname, the match fails for cross-module records and
489
+ # we fall through to the short-name fuzzy path.
490
+ info = self.ctx.registry.get_record(name)
491
+ qname = info.qualified_name() if info else None
492
+ resolved = NominalType(name, _module_qname=qname)
493
+ # Check exact match against union members
494
+ if resolved is not None:
495
+ if any(m == resolved for m in subject_type.members):
496
+ return resolved
497
+ # Known type but not a union member -- give specific error,
498
+ # unless it could match as a parameterized type (fall through)
499
+ if not any(getattr(m, 'name', None) == name for m in subject_type.members):
500
+ raise self.ctx.error(
501
+ f"'{name}' is not a member of union '{subject_type}'", pattern
502
+ )
503
+
504
+ # Fall back: search union members by base name (for parameterized types)
505
+ matches = [m for m in subject_type.members
506
+ if getattr(m, 'name', None) == name]
507
+ if len(matches) == 1:
508
+ return matches[0]
509
+ if len(matches) > 1:
510
+ # Try disambiguation using field type sub-patterns
511
+ # (e.g. Box(value=str()) narrows Box[str] | Box[int] to Box[str])
512
+ disambiguated = self._disambiguate_by_field_types(
513
+ name, matches, pattern)
514
+ if disambiguated is not None:
515
+ return disambiguated
516
+ raise self.ctx.error(
517
+ f"ambiguous '{name}' pattern: union has multiple {name} members",
518
+ pattern,
519
+ )
520
+ raise self.ctx.error(f"unknown type '{name}' in match pattern", pattern)
521
+
522
+ def _analyze_class_pattern_record(
523
+ self, pattern: TpyClassPattern, subject_type: NominalType,
524
+ bindings: dict[str, TpyType], stmt: TpyMatch,
525
+ ) -> None:
526
+ """Analyze a class pattern on a concrete record subject (field-value matching)."""
527
+ if not isinstance(pattern.cls, TpyName):
528
+ raise self.ctx.error(
529
+ "class pattern must use a simple name", pattern
530
+ )
531
+ cls_name = pattern.cls.name
532
+
533
+ record = self.ctx.registry.get_record(cls_name)
534
+ if record is None:
535
+ raise self.ctx.error(f"unknown type '{cls_name}' in match pattern", pattern)
536
+
537
+ if cls_name != subject_type.name:
538
+ raise self.ctx.error(
539
+ f"class pattern '{cls_name}' does not match "
540
+ f"subject type '{subject_type.name}'", pattern
541
+ )
542
+
543
+ pattern.resolved_type = subject_type
544
+ self._resolve_class_pattern_fields(pattern, record, bindings)
545
+
546
+ def _resolve_class_pattern_fields(
547
+ self, pattern: TpyClassPattern, record: RecordInfo,
548
+ bindings: dict[str, TpyType],
549
+ ) -> None:
550
+ """Resolve positional patterns and validate keyword field bindings."""
551
+ cls_name = pattern.cls.name if isinstance(pattern.cls, TpyName) else "?"
552
+
553
+ # Use match_args for positional resolution (if set by macro), own fields otherwise.
554
+ # For field type lookup, include inherited fields.
555
+ all_fields = self.ctx.registry.get_all_fields(record)
556
+ match_args = record.match_args if record.match_args is not None else tuple(
557
+ f.name for f in record.fields
558
+ )
559
+
560
+ # Resolve positional patterns to keyword patterns via match_args order
561
+ if pattern.positional:
562
+ n = len(match_args)
563
+ if len(pattern.positional) > n:
564
+ noun = "positional pattern" if n == 1 else "positional patterns"
565
+ raise self.ctx.error(
566
+ f"'{cls_name}' accepts {n} {noun} "
567
+ f"but {len(pattern.positional)} were given", pattern
568
+ )
569
+ kwd_names = {name for name, _ in pattern.keywords}
570
+ resolved: list[tuple[str, TpyPattern]] = []
571
+ for i, sub_pat in enumerate(pattern.positional):
572
+ field_name = match_args[i]
573
+ if field_name in kwd_names:
574
+ raise self.ctx.error(
575
+ f"field '{field_name}' is bound both positionally and by keyword "
576
+ f"in pattern for '{cls_name}'", pattern
577
+ )
578
+ resolved.append((field_name, sub_pat))
579
+ resolved.extend(pattern.keywords)
580
+ pattern.keywords = resolved
581
+ pattern.positional = []
582
+
583
+ # Build type param substitution map for generic records
584
+ type_subst = self._build_type_subst(record, pattern.resolved_type)
585
+
586
+ # Validate keyword field bindings
587
+ for field_name, sub_pattern in pattern.keywords:
588
+ field_info = None
589
+ for f in all_fields:
590
+ if f.name == field_name:
591
+ field_info = f
592
+ break
593
+ if field_info is None:
594
+ raise self.ctx.error(
595
+ f"'{cls_name}' has no field '{field_name}'", pattern
596
+ )
597
+ # Resolve field type with type param substitution
598
+ field_type = _subst_type_params(field_info.type, type_subst) if type_subst else field_info.type
599
+ # Sub-pattern bindings
600
+ if isinstance(sub_pattern, TpyCapturePattern):
601
+ bindings[sub_pattern.name] = field_type
602
+ elif isinstance(sub_pattern, TpyWildcardPattern):
603
+ pass
604
+ elif isinstance(sub_pattern, TpyLiteralPattern):
605
+ pass # Literal comparison -- validated at codegen time
606
+ elif isinstance(sub_pattern, TpyClassPattern):
607
+ # Type sub-pattern (e.g., value=str()) -- type guard
608
+ matched_type = self._validate_field_type_pattern(sub_pattern, field_type, pattern)
609
+ # Recursively validate nested field patterns
610
+ if sub_pattern.keywords or sub_pattern.positional:
611
+ self._resolve_nested_class_fields(sub_pattern, matched_type, bindings)
612
+ elif isinstance(sub_pattern, TpyAsPattern):
613
+ inner_sub = sub_pattern.pattern
614
+ if isinstance(inner_sub, TpyClassPattern):
615
+ # Type sub-pattern with capture (e.g., value=str() as v)
616
+ matched_type = self._validate_field_type_pattern(inner_sub, field_type, pattern)
617
+ bindings[sub_pattern.name] = matched_type
618
+ # Recursively validate nested field patterns
619
+ if inner_sub.keywords or inner_sub.positional:
620
+ self._resolve_nested_class_fields(inner_sub, matched_type, bindings)
621
+ elif isinstance(inner_sub, TpyLiteralPattern):
622
+ bindings[sub_pattern.name] = field_type
623
+ elif isinstance(inner_sub, (TpyWildcardPattern, TpyCapturePattern)):
624
+ bindings[sub_pattern.name] = field_type
625
+ if isinstance(inner_sub, TpyCapturePattern):
626
+ bindings[inner_sub.name] = field_type
627
+ else:
628
+ raise self.ctx.error(
629
+ f"Unsupported sub-pattern in field binding: "
630
+ f"{type(inner_sub).__name__}", sub_pattern
631
+ )
632
+ else:
633
+ raise self.ctx.error(
634
+ f"Unsupported sub-pattern in field binding: "
635
+ f"{type(sub_pattern).__name__}", sub_pattern
636
+ )
637
+
638
+ def _analyze_pattern_nonunion(
639
+ self, pattern: TpyPattern, subject_type: TpyType,
640
+ seen_values: set[object], bindings: dict[str, TpyType], stmt: TpyMatch,
641
+ ) -> None:
642
+ """Analyze a pattern for non-union subjects (enum, primitive, literal, wildcard, capture, as)."""
643
+ if isinstance(pattern, TpyWildcardPattern):
644
+ return
645
+
646
+ elif isinstance(pattern, TpyCapturePattern):
647
+ bindings[pattern.name] = subject_type
648
+
649
+ elif isinstance(pattern, TpyAsPattern):
650
+ self._analyze_pattern_nonunion(
651
+ pattern.pattern, subject_type, seen_values, bindings, stmt,
652
+ )
653
+ bindings[pattern.name] = subject_type
654
+
655
+ elif isinstance(pattern, TpyLiteralPattern):
656
+ self._validate_literal_pattern(pattern, subject_type)
657
+ self._check_duplicate_literal(pattern, seen_values)
658
+
659
+ elif isinstance(pattern, TpyValuePattern):
660
+ self._validate_value_pattern(pattern, subject_type)
661
+ self._check_duplicate_value(pattern, seen_values)
662
+
663
+ elif isinstance(pattern, TpyOrPattern):
664
+ self._analyze_or_pattern(pattern, subject_type, seen_values, bindings, stmt, kind=OrPatternKind.NONUNION)
665
+
666
+ else:
667
+ raise self.ctx.error(
668
+ f"Unsupported pattern for {subject_type} subject: "
669
+ f"{type(pattern).__name__}", pattern
670
+ )
671
+
672
+ def _analyze_pattern_record(
673
+ self, pattern: TpyPattern, subject_type: NominalType,
674
+ bindings: dict[str, TpyType], stmt: TpyMatch,
675
+ ) -> None:
676
+ """Analyze a pattern for concrete record subjects (field-value matching)."""
677
+ if isinstance(pattern, TpyWildcardPattern):
678
+ return
679
+
680
+ elif isinstance(pattern, TpyCapturePattern):
681
+ bindings[pattern.name] = subject_type
682
+
683
+ elif isinstance(pattern, TpyAsPattern):
684
+ self._analyze_pattern_record(
685
+ pattern.pattern, subject_type, bindings, stmt,
686
+ )
687
+ bindings[pattern.name] = subject_type
688
+
689
+ elif isinstance(pattern, TpyClassPattern):
690
+ self._analyze_class_pattern_record(pattern, subject_type, bindings, stmt)
691
+
692
+ elif isinstance(pattern, TpyOrPattern):
693
+ self._analyze_or_pattern(
694
+ pattern, subject_type, set(), bindings, stmt, kind=OrPatternKind.RECORD,
695
+ )
696
+
697
+ else:
698
+ raise self.ctx.error(
699
+ f"Unsupported pattern for record subject '{subject_type.name}': "
700
+ f"{type(pattern).__name__}", pattern
701
+ )
702
+
703
+ def _analyze_pattern_optional(
704
+ self, pattern: TpyPattern, subject_type: OptionalType,
705
+ seen_values: set[object], bindings: dict[str, TpyType], stmt: TpyMatch,
706
+ ) -> None:
707
+ """Analyze a pattern for Optional subjects."""
708
+ if isinstance(pattern, TpyWildcardPattern):
709
+ return
710
+
711
+ elif isinstance(pattern, TpyCapturePattern):
712
+ # Capture matches the non-None value (case None: is a literal pattern)
713
+ bindings[pattern.name] = subject_type.inner
714
+
715
+ elif isinstance(pattern, TpyAsPattern):
716
+ self._analyze_pattern_optional(
717
+ pattern.pattern, subject_type, seen_values, bindings, stmt,
718
+ )
719
+ bindings[pattern.name] = subject_type.inner
720
+
721
+ elif isinstance(pattern, TpyLiteralPattern):
722
+ if pattern.value is None:
723
+ self._check_duplicate_literal(pattern, seen_values)
724
+ else:
725
+ # Literal match on the inner type (e.g. case 42: on Optional[Int32])
726
+ self._validate_literal_pattern(pattern, subject_type.inner)
727
+ self._check_duplicate_literal(pattern, seen_values)
728
+
729
+ elif isinstance(pattern, TpyValuePattern):
730
+ # Value pattern on inner type (e.g. case Color.RED: on Optional[Color])
731
+ self._validate_value_pattern(pattern, subject_type.inner)
732
+ self._check_duplicate_value(pattern, seen_values)
733
+
734
+ elif isinstance(pattern, TpyClassPattern):
735
+ # Class pattern on the inner type (e.g. case Point(): on Optional[Point])
736
+ inner = subject_type.inner
737
+ if isinstance(inner, NominalType) and inner.is_user_record:
738
+ record = self.ctx.registry.get_record(inner.name)
739
+ if record is not None:
740
+ self._analyze_class_pattern_record(pattern, inner, bindings, stmt)
741
+ return
742
+ raise self.ctx.error(
743
+ f"class pattern not valid for Optional inner type '{inner}'",
744
+ pattern,
745
+ )
746
+
747
+ elif isinstance(pattern, TpyOrPattern):
748
+ self._analyze_or_pattern(
749
+ pattern, subject_type, seen_values, bindings, stmt, kind=OrPatternKind.OPTIONAL,
750
+ )
751
+
752
+ else:
753
+ raise self.ctx.error(
754
+ f"Unsupported pattern for Optional subject: "
755
+ f"{type(pattern).__name__}", pattern
756
+ )
757
+
758
+ def _analyze_or_pattern(
759
+ self, pattern: TpyOrPattern, subject_type: TpyType,
760
+ seen: set, bindings: dict[str, TpyType], stmt: TpyMatch,
761
+ kind: OrPatternKind,
762
+ ) -> None:
763
+ """Analyze an or-pattern: all alternatives must bind same variables with compatible types."""
764
+ if len(pattern.patterns) < 2:
765
+ raise self.ctx.error("or-pattern must have at least 2 alternatives", pattern)
766
+
767
+ first_bindings: dict[str, TpyType] | None = None
768
+ for alt in pattern.patterns:
769
+ alt_bindings: dict[str, TpyType] = {}
770
+ if kind is OrPatternKind.UNION:
771
+ self._analyze_pattern(alt, subject_type, seen, alt_bindings, stmt)
772
+ elif kind is OrPatternKind.RECORD:
773
+ self._analyze_pattern_record(alt, subject_type, alt_bindings, stmt)
774
+ elif kind is OrPatternKind.OPTIONAL:
775
+ self._analyze_pattern_optional(alt, subject_type, seen, alt_bindings, stmt)
776
+ else:
777
+ self._analyze_pattern_nonunion(alt, subject_type, seen, alt_bindings, stmt)
778
+
779
+ if first_bindings is None:
780
+ first_bindings = alt_bindings
781
+ else:
782
+ # Check same variable names
783
+ if set(alt_bindings.keys()) != set(first_bindings.keys()):
784
+ missing = set(first_bindings.keys()) - set(alt_bindings.keys())
785
+ extra = set(alt_bindings.keys()) - set(first_bindings.keys())
786
+ if missing:
787
+ raise self.ctx.error(
788
+ f"variable(s) {', '.join(sorted(missing))} not bound "
789
+ f"in all alternatives of or-pattern", alt
790
+ )
791
+ if extra:
792
+ raise self.ctx.error(
793
+ f"variable(s) {', '.join(sorted(extra))} not bound "
794
+ f"in all alternatives of or-pattern", alt
795
+ )
796
+ # Check compatible types
797
+ for name, ty in alt_bindings.items():
798
+ first_ty = first_bindings[name]
799
+ if ty != first_ty:
800
+ raise self.ctx.error(
801
+ f"variable '{name}' has type '{first_ty}' in first alternative "
802
+ f"but '{ty}' in another", alt
803
+ )
804
+
805
+ if first_bindings:
806
+ bindings.update(first_bindings)
807
+
808
+ def _validate_literal_pattern(
809
+ self, pattern: TpyLiteralPattern, subject_type: TpyType,
810
+ ) -> None:
811
+ """Validate that a literal pattern is compatible with the subject type."""
812
+ # Unwrap LiteralType to base_type for validation
813
+ check_type = subject_type.base_type if isinstance(subject_type, LiteralType) else subject_type
814
+ val = pattern.value
815
+ if val is None:
816
+ raise self.ctx.error(
817
+ "None literal pattern requires an Optional subject", pattern
818
+ )
819
+ if isinstance(val, bool):
820
+ if not is_bool_type(check_type):
821
+ raise self.ctx.error(
822
+ f"bool literal pattern not valid for subject type '{subject_type}'",
823
+ pattern,
824
+ )
825
+ elif isinstance(val, int):
826
+ if not (is_fixed_int_type(check_type) or is_big_int_type(check_type)
827
+ or is_enum_type(check_type)):
828
+ raise self.ctx.error(
829
+ f"int literal pattern not valid for subject type '{subject_type}'",
830
+ pattern,
831
+ )
832
+ elif isinstance(val, float):
833
+ if not is_float_type(check_type):
834
+ raise self.ctx.error(
835
+ f"float literal pattern not valid for subject type '{subject_type}'",
836
+ pattern,
837
+ )
838
+ elif isinstance(val, str):
839
+ if not is_any_str_type(check_type):
840
+ raise self.ctx.error(
841
+ f"str literal pattern not valid for subject type '{subject_type}'",
842
+ pattern,
843
+ )
844
+
845
+ def _extract_literal_pattern_values(
846
+ self, pattern: TpyPattern, lit_type: LiteralType,
847
+ ) -> list[LiteralValue] | None:
848
+ """Extract LiteralValues matched by a pattern. None for wildcard/capture."""
849
+ if isinstance(pattern, TpyAsPattern):
850
+ return self._extract_literal_pattern_values(pattern.pattern, lit_type)
851
+ if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
852
+ return None
853
+ if isinstance(pattern, TpyLiteralPattern):
854
+ val = pattern.value
855
+ tag = (LiteralTag.BOOL if isinstance(val, bool)
856
+ else LiteralTag.INT if isinstance(val, int)
857
+ else LiteralTag.STR)
858
+ return [LiteralValue(tag, val)]
859
+ if isinstance(pattern, TpyOrPattern):
860
+ result: list[LiteralValue] = []
861
+ for alt in pattern.patterns:
862
+ sub = self._extract_literal_pattern_values(alt, lit_type)
863
+ if sub is None:
864
+ return None
865
+ result.extend(sub)
866
+ return result
867
+ return None
868
+
869
+ def _validate_value_pattern(
870
+ self, pattern: TpyValuePattern, subject_type: TpyType,
871
+ ) -> None:
872
+ """Validate a value pattern (e.g., Color.RED) against the subject type."""
873
+ val_type = self.expr.analyze_expr(pattern.expr)
874
+ if is_enum_type(subject_type):
875
+ if not is_enum_type(val_type) or val_type.name != subject_type.name:
876
+ raise self.ctx.error(
877
+ f"value pattern type '{val_type}' does not match "
878
+ f"subject type '{subject_type}'", pattern
879
+ )
880
+ else:
881
+ raise self.ctx.error(
882
+ f"value pattern (dotted name) not valid for subject type "
883
+ f"'{subject_type}'", pattern
884
+ )
885
+
886
+ def _check_duplicate_literal(
887
+ self, pattern: TpyLiteralPattern, seen: set[object],
888
+ ) -> None:
889
+ """Check for duplicate literal pattern values."""
890
+ key = pattern.value
891
+ if key in seen:
892
+ label = repr(key)
893
+ raise self.ctx.error(
894
+ f"duplicate case for {label} in match statement", pattern
895
+ )
896
+ seen.add(key)
897
+
898
+ def _check_duplicate_value(
899
+ self, pattern: TpyValuePattern, seen: set[object],
900
+ ) -> None:
901
+ """Check for duplicate value pattern (e.g. Color.RED)."""
902
+ if isinstance(pattern.expr, TpyFieldAccess):
903
+ obj_name = pattern.expr.obj.name if isinstance(pattern.expr.obj, TpyName) else "?"
904
+ key = (obj_name, pattern.expr.field)
905
+ if key in seen:
906
+ raise self.ctx.error(
907
+ f"duplicate case for '{obj_name}.{pattern.expr.field}' "
908
+ f"in match statement", pattern
909
+ )
910
+ seen.add(key)
911
+
912
+ # ------------------------------------------------------------------
913
+ # Nested type sub-pattern helpers
914
+ # ------------------------------------------------------------------
915
+
916
+ @staticmethod
917
+ def _build_pattern_type_key(
918
+ pattern: TpyClassPattern, resolved_type: TpyType,
919
+ ) -> str:
920
+ """Build a key for duplicate-case detection that includes union field guards."""
921
+ key = str(resolved_type)
922
+ for _, sub in pattern.keywords:
923
+ inner = sub
924
+ if isinstance(inner, TpyAsPattern):
925
+ inner = inner.pattern
926
+ if isinstance(inner, TpyClassPattern) and inner.is_union_field_guard:
927
+ key += f"+{inner.resolved_type}"
928
+ return key
929
+
930
+ @staticmethod
931
+ def _is_constraining_sub_pattern(sub: TpyPattern) -> bool:
932
+ """Check if a field sub-pattern adds a constraint (not always-matching)."""
933
+ if isinstance(sub, (TpyLiteralPattern, TpyValuePattern, TpyClassPattern)):
934
+ return True
935
+ if isinstance(sub, TpyAsPattern) and isinstance(sub.pattern, TpyClassPattern):
936
+ return True
937
+ return False
938
+
939
+ @staticmethod
940
+ def _build_type_subst(
941
+ record: 'RecordInfo', resolved_type: TpyType | None,
942
+ ) -> dict[str, TpyType]:
943
+ """Build type param substitution map from resolved_type's type_args."""
944
+ if (resolved_type is None or not isinstance(resolved_type, NominalType)
945
+ or not record.type_params or not resolved_type.type_args):
946
+ return {}
947
+ subst: dict[str, TpyType] = {}
948
+ for param_name, arg in zip(record.type_params, resolved_type.type_args):
949
+ if isinstance(arg, TpyType):
950
+ subst[param_name] = arg
951
+ return subst
952
+
953
+ def _disambiguate_by_field_types(
954
+ self, cls_name: str, candidates: list[TpyType],
955
+ pattern: TpyClassPattern,
956
+ ) -> TpyType | None:
957
+ """Try to disambiguate multiple same-name union members using field type sub-patterns.
958
+
959
+ For example, Box(value=str()) on union Box[str] | Box[int] narrows to Box[str]
960
+ by checking which candidate's 'value' field type matches 'str'.
961
+
962
+ Recurses into nested type sub-patterns, so Box(value=Box(value=str()))
963
+ can disambiguate Box[Box[str]] | Box[Box[int]].
964
+
965
+ Returns the unique matching candidate, or None if no type guards are present.
966
+ Raises an error if type guards are present but no candidate matches.
967
+ """
968
+ record = self.ctx.registry.get_record(cls_name)
969
+ if record is None:
970
+ return None
971
+ if not self._has_type_sub_patterns(pattern, record):
972
+ return None
973
+
974
+ all_fields = self.ctx.registry.get_all_fields(record)
975
+ matching: list[TpyType] = []
976
+ for candidate in candidates:
977
+ if self._pattern_matches_candidate(record, candidate, all_fields, pattern):
978
+ matching.append(candidate)
979
+
980
+ if len(matching) == 1:
981
+ return matching[0]
982
+ if len(matching) == 0:
983
+ raise self.ctx.error(
984
+ f"no '{cls_name}' variant matches the field type pattern(s)",
985
+ pattern,
986
+ )
987
+ return None
988
+
989
+ def _has_type_sub_patterns(
990
+ self, pattern: TpyClassPattern, record: 'RecordInfo',
991
+ ) -> bool:
992
+ """Check if a class pattern has any type sub-patterns (TpyClassPattern in fields)."""
993
+ match_args = record.match_args if record.match_args is not None else tuple(
994
+ f.name for f in record.fields
995
+ )
996
+ for i, sub_pat in enumerate(pattern.positional):
997
+ if i >= len(match_args):
998
+ break
999
+ inner = sub_pat
1000
+ if isinstance(inner, TpyAsPattern):
1001
+ inner = inner.pattern
1002
+ if isinstance(inner, TpyClassPattern):
1003
+ return True
1004
+ for _, sub_pat in pattern.keywords:
1005
+ inner = sub_pat
1006
+ if isinstance(inner, TpyAsPattern):
1007
+ inner = inner.pattern
1008
+ if isinstance(inner, TpyClassPattern):
1009
+ return True
1010
+ return False
1011
+
1012
+ def _pattern_matches_candidate(
1013
+ self, record: 'RecordInfo', candidate: TpyType,
1014
+ all_fields: list, pattern: TpyClassPattern,
1015
+ ) -> bool:
1016
+ """Recursively check if all type sub-patterns in a class pattern match a candidate."""
1017
+ subst = self._build_type_subst(record, candidate)
1018
+
1019
+ # Build combined keyword list (positional resolved via match_args + explicit keywords)
1020
+ match_args = record.match_args if record.match_args is not None else tuple(
1021
+ f.name for f in record.fields
1022
+ )
1023
+ keywords: list[tuple[str, TpyPattern]] = [
1024
+ (match_args[i], sub_pat)
1025
+ for i, sub_pat in enumerate(pattern.positional)
1026
+ if i < len(match_args)
1027
+ ]
1028
+ keywords.extend(pattern.keywords)
1029
+
1030
+ for field_name, sub_pat in keywords:
1031
+ inner = sub_pat
1032
+ if isinstance(inner, TpyAsPattern):
1033
+ inner = inner.pattern
1034
+ if not isinstance(inner, TpyClassPattern) or not isinstance(inner.cls, TpyName):
1035
+ continue
1036
+
1037
+ type_name = inner.cls.name
1038
+ field_type = next((f.type for f in all_fields if f.name == field_name), None)
1039
+ if field_type is None:
1040
+ return False
1041
+ resolved = _subst_type_params(field_type, subst) if subst else field_type
1042
+
1043
+ if not self._type_pattern_compatible(type_name, resolved, inner):
1044
+ return False
1045
+ return True
1046
+
1047
+ def _type_pattern_compatible(
1048
+ self, type_name: str, field_type: TpyType,
1049
+ inner_pattern: TpyClassPattern,
1050
+ ) -> bool:
1051
+ """Check if a type name matches a field type, recursing into inner patterns."""
1052
+ # Resolve to a concrete type
1053
+ pattern_type = _resolve_concrete_type_name(type_name)
1054
+ if pattern_type is not None:
1055
+ if field_type == pattern_type:
1056
+ return True
1057
+ if isinstance(field_type, UnionType):
1058
+ return any(m == pattern_type for m in field_type.members)
1059
+ return False
1060
+
1061
+ # User record: match by name
1062
+ inner_record = self.ctx.registry.get_record(type_name)
1063
+ if inner_record is None:
1064
+ return False
1065
+
1066
+ # Collect matching types from field_type
1067
+ if isinstance(field_type, NominalType) and field_type.name == type_name:
1068
+ candidates = [field_type]
1069
+ elif isinstance(field_type, UnionType):
1070
+ candidates = [m for m in field_type.members
1071
+ if isinstance(m, NominalType) and m.name == type_name]
1072
+ else:
1073
+ return False
1074
+
1075
+ if not candidates:
1076
+ return False
1077
+
1078
+ # If the inner pattern has further type sub-patterns, use them to narrow
1079
+ has_inner = (inner_pattern.keywords or inner_pattern.positional) and self._has_type_sub_patterns(inner_pattern, inner_record)
1080
+ if has_inner:
1081
+ inner_fields = self.ctx.registry.get_all_fields(inner_record)
1082
+ candidates = [c for c in candidates
1083
+ if self._pattern_matches_candidate(
1084
+ inner_record, c, inner_fields, inner_pattern)]
1085
+
1086
+ return len(candidates) >= 1
1087
+
1088
+ def _validate_field_type_pattern(
1089
+ self, sub_pattern: TpyClassPattern, field_type: TpyType,
1090
+ parent: TpyPattern,
1091
+ ) -> TpyType:
1092
+ """Validate a type sub-pattern against the resolved field type.
1093
+
1094
+ Returns the matched type (for binding). Sets sub_pattern.resolved_type
1095
+ when the field is a union (signals codegen to emit holds_alternative).
1096
+ """
1097
+ if not isinstance(sub_pattern.cls, TpyName):
1098
+ raise self.ctx.error(
1099
+ "type pattern in field must use a simple name", sub_pattern
1100
+ )
1101
+ cls_name = sub_pattern.cls.name
1102
+
1103
+ pattern_type = _resolve_concrete_type_name(cls_name)
1104
+ if pattern_type is None:
1105
+ info = self.ctx.registry.get_record(cls_name)
1106
+ if info is not None:
1107
+ pattern_type = NominalType(cls_name, _module_qname=info.qualified_name())
1108
+ if pattern_type is None:
1109
+ raise self.ctx.error(
1110
+ f"unknown type '{cls_name}' in field type pattern", sub_pattern
1111
+ )
1112
+
1113
+ # Exact match: compile-time type guard (no runtime check)
1114
+ if field_type == pattern_type:
1115
+ return field_type
1116
+ if isinstance(field_type, NominalType) and field_type.name == cls_name:
1117
+ return field_type
1118
+
1119
+ # Union field: check if field_type is a union containing pattern_type
1120
+ is_record = isinstance(pattern_type, NominalType)
1121
+ if isinstance(field_type, UnionType):
1122
+ if is_record:
1123
+ # Records: match by name (handles parameterized types like Box[str])
1124
+ matches = [m for m in field_type.members
1125
+ if isinstance(m, NominalType) and m.name == cls_name]
1126
+ else:
1127
+ # Primitives: exact type match
1128
+ matches = [m for m in field_type.members if m == pattern_type]
1129
+
1130
+ if len(matches) == 1:
1131
+ sub_pattern.resolved_type = matches[0]
1132
+ sub_pattern.is_union_field_guard = True
1133
+ return matches[0]
1134
+
1135
+ if len(matches) > 1:
1136
+ # Multiple same-name members -- try inner patterns for disambiguation
1137
+ inner_record = self.ctx.registry.get_record(cls_name)
1138
+ if inner_record is not None and (sub_pattern.keywords or sub_pattern.positional):
1139
+ disambiguated = self._disambiguate_by_field_types(
1140
+ cls_name, matches, sub_pattern)
1141
+ if disambiguated is not None:
1142
+ sub_pattern.resolved_type = disambiguated
1143
+ sub_pattern.is_union_field_guard = True
1144
+ return disambiguated
1145
+ raise self.ctx.error(
1146
+ f"ambiguous '{cls_name}' in field union type '{field_type}'",
1147
+ sub_pattern,
1148
+ )
1149
+
1150
+ raise self.ctx.error(
1151
+ f"type '{cls_name}' is not a member of "
1152
+ f"field union type '{field_type}'", sub_pattern
1153
+ )
1154
+
1155
+ raise self.ctx.error(
1156
+ f"type pattern '{cls_name}' does not match "
1157
+ f"field type '{field_type}'", sub_pattern
1158
+ )
1159
+
1160
+ def _resolve_nested_class_fields(
1161
+ self, sub_pattern: TpyClassPattern, matched_type: TpyType,
1162
+ bindings: dict[str, TpyType],
1163
+ ) -> None:
1164
+ """Recursively resolve field patterns on a nested class sub-pattern."""
1165
+ if not isinstance(sub_pattern.cls, TpyName):
1166
+ return
1167
+ cls_name = sub_pattern.cls.name
1168
+ record = self.ctx.registry.get_record(cls_name)
1169
+ if record is None:
1170
+ raise self.ctx.error(
1171
+ f"type '{cls_name}' does not support field patterns", sub_pattern
1172
+ )
1173
+ # resolved_type is already set by _validate_field_type_pattern for
1174
+ # union fields; for exact matches, set it for type param substitution
1175
+ if sub_pattern.resolved_type is None:
1176
+ sub_pattern.resolved_type = matched_type
1177
+ self._resolve_class_pattern_fields(sub_pattern, record, bindings)