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/parse/nodes.py ADDED
@@ -0,0 +1,1732 @@
1
+ """
2
+ TurboPython AST node definitions.
3
+
4
+ Pure data definitions: dataclasses, ParseError, SourceLocation.
5
+ No parsing logic lives here.
6
+ """
7
+
8
+ from __future__ import annotations
9
+ import ast
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum, IntEnum
12
+ from typing import Any, Callable, Literal, Optional, TYPE_CHECKING
13
+
14
+ from ..typesys import (
15
+ TpyType, NominalType, FieldInfo, FunctionInfo,
16
+ MethodSignature, TypeParamKind, LiteralValue,
17
+ )
18
+
19
+
20
+ # Source location for error reporting and source mapping
21
+
22
+ @dataclass
23
+ class SourceLocation:
24
+ """Source code location for error reporting and source mapping."""
25
+ line: int # 1-indexed line number
26
+ column: int = 0 # 0-indexed column
27
+ file: str | None = None # Source file path (optional)
28
+
29
+
30
+ class ParseError(Exception):
31
+ """Error during parsing.
32
+
33
+ Accepts either an ast.AST node (for parse-time errors where the ast is
34
+ available) or a SourceLocation (for resolver-time errors where only the
35
+ walked TypeRefNode's loc is known). Lineno is populated from whichever
36
+ is provided.
37
+ """
38
+ def __init__(self, message: str, node: Optional[ast.AST] = None,
39
+ *, loc: Optional['SourceLocation'] = None):
40
+ self.node = node
41
+ self.message = message
42
+ if node is not None and hasattr(node, 'lineno'):
43
+ self.lineno = node.lineno
44
+ elif loc is not None:
45
+ self.lineno = loc.line
46
+ else:
47
+ self.lineno = None
48
+ loc_str = f" at line {self.lineno}" if self.lineno else ""
49
+ super().__init__(f"{message}{loc_str}")
50
+
51
+ def format(self, filename: str = "<unknown>") -> str:
52
+ """Format error with file:line prefix."""
53
+ if self.lineno:
54
+ return f"{filename}:{self.lineno}: error: {self.message}"
55
+ return f"{filename}: error: {self.message}"
56
+
57
+
58
+ class ResolutionFailure(ParseError):
59
+ """Recoverable name-resolution failure raised by `TypeResolver`.
60
+
61
+ Signals that a `TpyTypeRef` referred to a name the resolver could not
62
+ bind ("Unknown type", "Unknown generic type", "Unsupported qualified
63
+ type"). Lenient callers (macro-fragment resolution) catch this
64
+ specifically and substitute a `NominalType(name, args)` placeholder;
65
+ everyone else treats it as a regular `ParseError`.
66
+ """
67
+
68
+
69
+ # Unresolved type-syntax nodes.
70
+ #
71
+ # Parser emits these in annotation positions instead of constructing TpyType
72
+ # directly. Sema's resolve_type_ref walks them against the TypeDef registry,
73
+ # local type-parameter scope, and structural wrappers to produce TpyType.
74
+ #
75
+ # Four node kinds cover all Python type syntax:
76
+ # - TpyTypeRef : Name or Name[args]. Handles primitives, generics,
77
+ # structural wrappers expressed via subscript
78
+ # (Ptr[T], Own[T], Optional[T], Readonly[T], tuple[T1,T2],
79
+ # Array[T, N], ...), qualified names (Outer.Inner), type
80
+ # parameters, Self, None. Integer args (Array[T, N])
81
+ # sit alongside type args.
82
+ # - TpyUnionRef : T | U | ... (Python BinOp with BitOr).
83
+ # - TpyCallableRef : Callable[[P1, P2], R] or Fn[[P1, P2], R] -- the
84
+ # list-shaped param group doesn't fit a uniform args
85
+ # tuple.
86
+ # - TpyLiteralRef : Literal[v1, v2, ...] where the args are values, not
87
+ # types.
88
+
89
+ @dataclass(frozen=True)
90
+ class TpyTypeRef:
91
+ """Named type reference with optional type arguments.
92
+
93
+ `name` is the raw source identifier, possibly dotted for qualified
94
+ references ("Outer.Inner", "module.Name"). Resolution (primitive lookup,
95
+ builtin/user registry lookup, type-parameter substitution, enum/record
96
+ qname minting) happens in sema.
97
+ """
98
+ name: str
99
+ args: tuple['ResolverInputNode | int', ...] = ()
100
+ loc: SourceLocation | None = None
101
+
102
+
103
+ @dataclass(frozen=True)
104
+ class TpyUnionRef:
105
+ """Union-syntax type reference: T | U | ..."""
106
+ members: tuple['ResolverInputNode', ...]
107
+ loc: SourceLocation | None = None
108
+
109
+
110
+ @dataclass(frozen=True)
111
+ class TpyCallableRef:
112
+ """Callable[[P1, P2], R] or Fn[[P1, P2], R]."""
113
+ kind: Literal["Callable", "Fn"]
114
+ params: tuple['ResolverInputNode', ...]
115
+ return_type: 'ResolverInputNode'
116
+ loc: SourceLocation | None = None
117
+
118
+
119
+ @dataclass(frozen=True)
120
+ class TpyLiteralRef:
121
+ """Literal[v1, v2, ...] -- values, not types."""
122
+ values: tuple[LiteralValue, ...]
123
+ loc: SourceLocation | None = None
124
+
125
+
126
+ @dataclass(frozen=True)
127
+ class TpyInferFromDefaultRef:
128
+ """Pending marker used by the parser on a FieldInfo whose type could
129
+ not be inferred from its default-value expression at parse time
130
+ (e.g. `Red = auto()` in a class whose `Enum` base was shadowed).
131
+
132
+ Emitted instead of raising "Cannot infer type for field 'X'" at parse
133
+ time so that any sema-time base resolution errors on the enclosing
134
+ record fire first. The post-parse `resolve_refs` pass handles this
135
+ node explicitly in the field loop and surfaces the inference error
136
+ as a SemanticError only when base resolution has already succeeded.
137
+ """
138
+ loc: SourceLocation | None = None
139
+
140
+
141
+ # ResolverInputNode is the subset of TypeRefNode that TypeResolver.resolve
142
+ # accepts: four parser-walker outputs (named ref, union, callable, literal).
143
+ # TpyInferFromDefaultRef is NOT a resolver input -- it's a field-storage
144
+ # marker that sema catches explicitly before calling the resolver.
145
+ type ResolverInputNode = TpyTypeRef | TpyUnionRef | TpyCallableRef | TpyLiteralRef
146
+ type TypeRefNode = ResolverInputNode | TpyInferFromDefaultRef
147
+
148
+
149
+ # AST node types for TurboPython
150
+
151
+ @dataclass
152
+ class TpyExpr:
153
+ """Base class for expressions."""
154
+ loc: SourceLocation | None = field(default=None, kw_only=True)
155
+
156
+ def children(self) -> list['TpyExpr']:
157
+ """Return child expression nodes for generic tree walking."""
158
+ return []
159
+
160
+
161
+ if TYPE_CHECKING:
162
+ from ..coercions import Coercion
163
+ from ..typesys import FunctionInfo, RecordInfo, ResolvedBinop, ResolvedUnaryop
164
+
165
+
166
+ @dataclass
167
+ class TpyIntLiteral(TpyExpr):
168
+ """Integer literal."""
169
+ value: int
170
+
171
+
172
+ @dataclass
173
+ class TpyFloatLiteral(TpyExpr):
174
+ """Floating point literal."""
175
+ value: float
176
+
177
+
178
+ @dataclass
179
+ class TpyStrLiteral(TpyExpr):
180
+ """String literal."""
181
+ value: str
182
+
183
+
184
+ @dataclass
185
+ class TpyBytesLiteral(TpyExpr):
186
+ """Bytes literal (b"...")."""
187
+ value: bytes
188
+
189
+
190
+ # F-string conversion codes (from CPython's ast module)
191
+ FSTRING_CONV_NONE = -1
192
+ FSTRING_CONV_STR = 115 # !s
193
+ FSTRING_CONV_REPR = 114 # !r
194
+ FSTRING_CONV_ASCII = 97 # !a
195
+
196
+
197
+ @dataclass
198
+ class TpyFStringValue:
199
+ """Formatted expression inside an f-string: {expr:spec}."""
200
+ expr: TpyExpr
201
+ conversion: int = FSTRING_CONV_NONE
202
+ format_spec: str | None = None
203
+
204
+
205
+ @dataclass
206
+ class TpyFString(TpyExpr):
207
+ """F-string: f"text {expr:spec} more text"."""
208
+ parts: list[str | TpyFStringValue]
209
+
210
+ def children(self) -> list[TpyExpr]:
211
+ return [p.expr for p in self.parts if isinstance(p, TpyFStringValue)]
212
+
213
+
214
+ @dataclass
215
+ class TpyBoolLiteral(TpyExpr):
216
+ """Boolean literal."""
217
+ value: bool
218
+
219
+
220
+ @dataclass
221
+ class TpyNoneLiteral(TpyExpr):
222
+ """None literal."""
223
+ pass
224
+
225
+
226
+ @dataclass
227
+ class TpyName(TpyExpr):
228
+ """Variable reference."""
229
+ name: str
230
+ is_function_ref: bool = False # Set by sema: name resolves to a function used as a value
231
+ function_ref_info: 'FunctionInfo | None' = None # Set by sema: resolved function for codegen
232
+ function_ref_type_args: 'tuple[TpyType, ...] | None' = None # Set by sema: inferred type args for generic function refs
233
+
234
+
235
+ @dataclass
236
+ class TpyBinOp(TpyExpr):
237
+ """Binary operation."""
238
+ left: TpyExpr
239
+ op: str # '+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>='
240
+ right: TpyExpr
241
+ resolved_binop: 'ResolvedBinop | None' = None # Set by sema for builtin ops
242
+ resolved_contains: 'FunctionInfo | None' = None # Set by sema for 'in'/'not in' with __contains__
243
+ typed_dict_in_field: str | None = None # Set by sema: "key" in TypedDict -> field presence check
244
+ typed_dict_in_always_true: bool = False # Set by sema: total=True field, fold to True
245
+ optional_safe_eq: bool = False # Set by sema: ==/!= with Optional value-type operand(s)
246
+ int_enum_coercion: 'NominalType | None' = None # Set by sema: IntEnum arithmetic coerced to underlying type
247
+ divisor_non_zero: bool = False # Set by sema: divisor provably non-zero, skip div-zero check
248
+
249
+ def children(self) -> list[TpyExpr]:
250
+ return [self.left, self.right]
251
+
252
+
253
+ @dataclass
254
+ class TpyChainedCompare(TpyExpr):
255
+ """Chained comparison: a < b < c desugars to (a < b) and (b < c)."""
256
+ left: TpyExpr
257
+ ops: list[str]
258
+ comparators: list[TpyExpr]
259
+ # Set by sema: synthetic TpyBinOp for each comparison pair
260
+ pairs: list['TpyBinOp'] | None = None
261
+
262
+ def children(self) -> list[TpyExpr]:
263
+ return [self.left] + self.comparators
264
+
265
+
266
+ @dataclass
267
+ class TpyUnaryOp(TpyExpr):
268
+ """Unary operation."""
269
+ op: str # '-', 'not'
270
+ operand: TpyExpr
271
+ resolved_unaryop: 'ResolvedUnaryop | None' = None # Set by sema for builtin ops
272
+
273
+ def children(self) -> list[TpyExpr]:
274
+ return [self.operand]
275
+
276
+
277
+ @dataclass
278
+ class TpyTypeParamConstruct(TpyExpr):
279
+ """Default-construction of a type parameter: T() in a default value."""
280
+ param_name: str
281
+
282
+
283
+ @dataclass
284
+ class TpyVarargPack(TpyExpr):
285
+ """Pack of variadic arguments at a call site (created by sema, not parser).
286
+
287
+ Represents the trailing args that get packed into a Span[readonly[T]] for
288
+ a *args: T parameter.
289
+ """
290
+ args: list[TpyExpr]
291
+ element_type: TpyType
292
+
293
+ def children(self) -> list[TpyExpr]:
294
+ return list(self.args)
295
+
296
+
297
+ @dataclass
298
+ class TpyStarUnpack(TpyExpr):
299
+ """Star unpacking at a call site: f(*expr). Created by parser for *expr in calls."""
300
+ expr: TpyExpr
301
+
302
+ def children(self) -> list[TpyExpr]:
303
+ return [self.expr]
304
+
305
+
306
+ @dataclass
307
+ class TpyCall(TpyExpr):
308
+ """Function or constructor call.
309
+
310
+ For generic function calls like first[Int32](items):
311
+ - type_args stores the explicit type arguments (e.g., (Int32,))
312
+ - inferred_type_args is set by sema for codegen (resolved from inference or explicit)
313
+ """
314
+ func: TpyExpr # TpyName for simple calls; arbitrary TpyExpr for expression callees
315
+ args: list[TpyExpr]
316
+ # Between parse and the post-parse resolve_refs pass,
317
+ # call_type and type_args may hold TypeRefNode in place of TpyType.
318
+ # All readers post-pre-pass see TpyType.
319
+ call_type: 'TpyType | TypeRefNode | None' = None # For generic instantiation like MyContainer[T, N]()
320
+ type_args: 'tuple[TpyType | TypeRefNode | None, ...]' = () # Explicit type args for generic function calls: func[T](args)
321
+ inferred_type_args: tuple[TpyType, ...] | None = None # Set by sema for generic function calls
322
+ type_args_parse_error: str | None = None # Set if subscript had args that couldn't be parsed as types
323
+ subscript_callee: 'TpyExpr | None' = None # Set by parser: fns[0](args) -> stores TpySubscript(fns, 0) for sema fallback
324
+ kwargs: dict[str, TpyExpr] = field(default_factory=dict) # Keyword arguments (limited support)
325
+ double_star_unpack: 'TpyExpr | None' = None # **expr unpacking at call site
326
+ kwarg_td_call: 'TpyExpr | None' = None # Set by sema: synthetic TypedDict construction for **kwargs
327
+ resolved_import: tuple[str, str] | None = None # Set by parser: (module, name) for resolved imports
328
+ resolved_function_info: FunctionInfo | None = None # Set by sema for resolved function overloads
329
+ enum_from_value: NominalType | None = None # Set by sema for enum value lookup: Color(0)
330
+ isinstance_var: str | None = None # Set by sema: variable name being isinstance-checked
331
+ isinstance_type: TpyType | None = None # Set by sema: resolved type being checked for
332
+ isinstance_is_protocol: bool = False # Set by sema: protocol isinstance (if constexpr)
333
+ # Set by sema (>0) when the isinstance source is an owning wrapper
334
+ # (Box[Pet]/Rc[Pet]): the dispatch target is the polymorphic payload
335
+ # reached through `depth` reference-returning __deref__ steps, not the
336
+ # variable itself. Drives deref-view narrowing (the wrapper's own type is
337
+ # never narrowed) and the deref-payload-pointer cast in codegen.
338
+ isinstance_deref_depth: int = 0
339
+ cast_target_type: TpyType | None = None # Set by sema for typing.cast(T, x): the resolved target type
340
+ cast_source_is_any: bool = False # Set by sema for typing.cast: True iff source's static type is Any
341
+ macro_expansion: 'TpyExpr | None' = None # Set by sema: replacement expr from @call_macro
342
+ dunder_call: 'TpyMethodCall | None' = None # Set by sema: obj(args) -> obj.__call__(args)
343
+ # D16 v1.5: try/catch lambda forms. Inner __getattr__ call is stored here;
344
+ # codegen wraps it in a lambda that catches AttributeError.
345
+ dyn_hasattr_call: 'TpyMethodCall | None' = None # hasattr(obj, "name") -> bool
346
+ dyn_getattr_default_call: 'TpyMethodCall | None' = None # getattr(obj, "name", default) -> T
347
+ # Method-local type params marked representational (`Ptr[U] -> Ptr[T]` in
348
+ # the callee body) whose substituted bound is @dynamic AND whose inferred
349
+ # type arg is a structural conformer needing Adapter wrap. Set by sema
350
+ # during call analysis; consulted by codegen to substitute the C++ template
351
+ # arg `U -> Adapter<T_sub, U_sub>`. None when the call doesn't need any
352
+ # substitution. Empty frozenset is never written -- absence is None.
353
+ representational_subst_params: frozenset[str] | None = None
354
+
355
+ @property
356
+ def func_name(self) -> str:
357
+ """Name of the callee. Only valid when func is a TpyName (simple call)."""
358
+ assert isinstance(self.func, TpyName), f"func_name on non-Name callee: {type(self.func).__name__}"
359
+ return self.func.name
360
+
361
+ @property
362
+ def maybe_func_name(self) -> str | None:
363
+ """Name of the callee, or None if the callee isn't a TpyName
364
+ (subscript-callee `arr[0]()`, expression callee `(get())()` etc.).
365
+ Use when probing whether a TpyCall targets a known free function
366
+ without first narrowing the callee shape.
367
+ """
368
+ return self.func.name if isinstance(self.func, TpyName) else None
369
+
370
+ def children(self) -> list[TpyExpr]:
371
+ if self.macro_expansion is not None:
372
+ return [self.macro_expansion]
373
+ extra = [self.subscript_callee] if self.subscript_callee is not None else []
374
+ return [self.func] + list(self.args) + list(self.kwargs.values()) + extra
375
+
376
+
377
+ @dataclass
378
+ class TpyMethodCall(TpyExpr):
379
+ """Method call on an object."""
380
+ obj: TpyExpr
381
+ method: str
382
+ args: list[TpyExpr]
383
+ kwargs: dict[str, TpyExpr] = field(default_factory=dict)
384
+ double_star_unpack: 'TpyExpr | None' = None # **expr unpacking at call site
385
+ resolved_import: tuple[str, str] | None = None # Set by parser: (module, name) for resolved imports
386
+ # May hold TypeRefNode pre-sema-pre-pass.
387
+ type_args: 'tuple[TpyType | TypeRefNode | None, ...]' = () # Explicit type args for module.func[T](args) syntax
388
+ type_args_parse_error: str | None = None # Set if subscript had args that couldn't be parsed as types
389
+ is_static_call: bool = False # Set by sema for ClassName.staticmethod() calls
390
+ super_parent_type: Optional[TpyType] = None # Set by sema for super().method() calls
391
+ unbound_self_parent_type: Optional[TpyType] = None # Set by sema for BaseN.method(self, ...) calls on an ancestor
392
+ user_module_call: Optional[str] = None # Set by sema for module.func() calls to user modules
393
+ builtin_module_call: Optional[str] = None # Set by sema for builtin module.func() calls (canonical module name)
394
+ needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
395
+ resolved_function_info: FunctionInfo | None = None # Set by sema for resolved method overloads
396
+ inferred_type_args: tuple[TpyType, ...] | None = None # Set by sema for generic builtin module calls
397
+ deref_depth: int = 0 # Set by sema: number of __deref__ steps applied before method resolution
398
+ # Set by sema to the narrowed subclass when this method resolves through a
399
+ # deref-view narrowing (`if isinstance(rc, Dog): rc.bark()`); codegen casts
400
+ # the deref payload pointer to it instead of emitting a plain __deref__ call.
401
+ deref_narrowed_to: TpyType | None = None
402
+ ptr_non_null: bool = False # Set by sema: receiver is a provably non-null Ptr (or Ptr[readonly[T]])
403
+ is_callable_field: bool = False # Set by sema: method name is a Callable-typed field
404
+ macro_expansion: 'TpyExpr | None' = None # Set by sema: replacement expr from @call_macro
405
+ typed_dict_get_field: str | None = None # Set by sema: td.get("key") -> field access
406
+ typed_dict_get_optional: bool = False # Set by sema: total=False field, absent by default
407
+ fstr_expansion: 'TpyExpr | None' = None # Set by sema: inlined FStr method body
408
+ is_nested_constructor: bool = False # Set by sema: Outer.Inner() nested record constructor
409
+ is_nested_enum_constructor: bool = False # Set by sema: Outer.Kind(v) nested enum from_value
410
+ nested_type_name: str | None = None # Set by sema: dotted name for nested type calls
411
+ # See TpyCall.representational_subst_params for the contract.
412
+ representational_subst_params: frozenset[str] | None = None
413
+
414
+ def children(self) -> list[TpyExpr]:
415
+ if self.fstr_expansion is not None:
416
+ return [self.fstr_expansion]
417
+ if self.macro_expansion is not None:
418
+ return [self.macro_expansion]
419
+ return [self.obj] + list(self.args) + list(self.kwargs.values())
420
+
421
+
422
+ @dataclass
423
+ class TpyFieldAccess(TpyExpr):
424
+ """Field access on a value or pointer."""
425
+ obj: TpyExpr
426
+ field: str
427
+ needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
428
+ deref_depth: int = 0 # Set by sema: number of __deref__ steps applied before field lookup
429
+ # Set by sema to the narrowed subclass when this field access resolves
430
+ # through a deref-view narrowing; codegen casts the deref payload pointer.
431
+ deref_narrowed_to: TpyType | None = None
432
+ ptr_non_null: bool = False # Set by sema: receiver is a provably non-null Ptr (or Ptr[readonly[T]])
433
+ is_property_access: bool = False # Set by sema: this is a property getter
434
+ property_setter: bool = False # Set by sema: assignment target is a property setter
435
+ property_getter_call: 'TpyMethodCall | None' = None # Set by sema: getter method call for codegen
436
+ property_setter_call: 'TpyMethodCall | None' = None # Set by sema: setter method call for codegen
437
+ dyn_getattr_call: 'TpyMethodCall | None' = None # Set by sema: __getattr__ fallback method call (D16)
438
+ dyn_setattr_call: 'TpyMethodCall | None' = None # Set by sema: __setattr__ fallback method call (D16)
439
+ dyn_delattr_call: 'TpyMethodCall | None' = None # Set by sema: __delattr__ fallback method call (D16)
440
+ unbound_self_parent_type: Optional[TpyType] = None # Set by sema for BaseN.field access on an ancestor subobject
441
+ class_constant_owner: Optional['RecordInfo'] = None # Set by sema: RecordInfo for ClassName.X class-constant access; codegen emits <cpp_qname>::<member>
442
+ module_var_access: Optional[tuple[str, str]] = None # Set by sema for `pkg.sub.X` variable access on a dotted module: (module_qname, var_name)
443
+
444
+ def children(self) -> list[TpyExpr]:
445
+ return [self.obj]
446
+
447
+
448
+ @dataclass
449
+ class TpyArrayLiteral(TpyExpr):
450
+ """Array literal: [expr, expr, ...]"""
451
+ elements: list[TpyExpr]
452
+
453
+ def children(self) -> list[TpyExpr]:
454
+ return list(self.elements)
455
+
456
+
457
+ class TupleElemCapture(IntEnum):
458
+ """How a tuple literal element captures its value."""
459
+ VALUE = 0 # T -- owned copy
460
+ REF = 1 # T& -- mutable reference
461
+ CONST_REF = 2 # const T& -- immutable reference
462
+
463
+
464
+ @dataclass
465
+ class TpyTupleLiteral(TpyExpr):
466
+ """Tuple literal: (expr, expr, ...)"""
467
+ elements: list[TpyExpr]
468
+ # Set by sema: per-element capture mode
469
+ elem_capture: list[TupleElemCapture] = field(default_factory=list)
470
+
471
+ def children(self) -> list[TpyExpr]:
472
+ return list(self.elements)
473
+
474
+
475
+ @dataclass
476
+ class TpyListRepeat(TpyExpr):
477
+ """List repetition: [elements...] * count -> sequence repeated count times"""
478
+ elements: list[TpyExpr]
479
+ count: TpyExpr
480
+
481
+ def children(self) -> list[TpyExpr]:
482
+ return list(self.elements) + [self.count]
483
+
484
+
485
+ @dataclass
486
+ class TpyComprehensionGenerator:
487
+ """Single generator clause: for var in iterable [if cond]*"""
488
+ var: str
489
+ iterable: TpyExpr
490
+ conditions: list[TpyExpr]
491
+ unpack_vars: list[str | None] | None = None # Phase 3: tuple unpacking
492
+ const_loop_var: bool = False
493
+
494
+
495
+ @dataclass
496
+ class TpyListComprehension(TpyExpr):
497
+ """List comprehension: [expr for var in iterable if cond]"""
498
+ element_expr: TpyExpr
499
+ generator: TpyComprehensionGenerator
500
+ result_elem_type: 'TpyType | None' = None # set by sema
501
+
502
+ def children(self) -> list[TpyExpr]:
503
+ return [self.element_expr, self.generator.iterable] + self.generator.conditions
504
+
505
+
506
+ @dataclass
507
+ class TpyDictComprehension(TpyExpr):
508
+ """Dict comprehension: {key: value for var in iterable if cond}"""
509
+ key_expr: TpyExpr
510
+ value_expr: TpyExpr
511
+ generator: TpyComprehensionGenerator
512
+ result_key_type: 'TpyType | None' = None # set by sema
513
+ result_value_type: 'TpyType | None' = None # set by sema
514
+
515
+ def children(self) -> list[TpyExpr]:
516
+ return [self.key_expr, self.value_expr, self.generator.iterable] + self.generator.conditions
517
+
518
+
519
+ @dataclass
520
+ class TpyDictLiteral(TpyExpr):
521
+ """Dict literal: {key: value, key: value, ...}"""
522
+ keys: list[TpyExpr]
523
+ values: list[TpyExpr]
524
+
525
+ def children(self) -> list[TpyExpr]:
526
+ return list(self.keys) + list(self.values)
527
+
528
+
529
+ @dataclass
530
+ class TpySetComprehension(TpyExpr):
531
+ """Set comprehension: {expr for var in iterable if cond}"""
532
+ element_expr: TpyExpr
533
+ generator: TpyComprehensionGenerator
534
+ result_elem_type: 'TpyType | None' = None # set by sema
535
+
536
+ def children(self) -> list[TpyExpr]:
537
+ return [self.element_expr, self.generator.iterable] + self.generator.conditions
538
+
539
+
540
+ @dataclass
541
+ class TpyGeneratorExpression(TpyExpr):
542
+ """Generator expression: (expr for var in iterable if cond)"""
543
+ element_expr: TpyExpr
544
+ generator: TpyComprehensionGenerator
545
+ result_elem_type: 'TpyType | None' = None # set by sema
546
+
547
+ def children(self) -> list[TpyExpr]:
548
+ return [self.element_expr, self.generator.iterable] + self.generator.conditions
549
+
550
+
551
+ @dataclass
552
+ class TpyLambda(TpyExpr):
553
+ """Lambda expression: lambda x, y: x + y.
554
+
555
+ Parameter types are inferred from context (Fn type hint) during sema.
556
+ """
557
+ param_names: list[str]
558
+ body: TpyExpr
559
+ inferred_param_types: list['TpyType'] = field(default_factory=list)
560
+ inferred_return_type: 'TpyType | None' = None
561
+ captured_names: list[str] = field(default_factory=list)
562
+ captures_by_value: bool = False # True for Callable context (captures escape)
563
+ readonly_params: bool = False # True when params should be const (key functions)
564
+
565
+ def children(self) -> list[TpyExpr]:
566
+ # Lambda creates its own scope; outer walks should not recurse into
567
+ # the body. Lambda analysis in sema recurses into body explicitly.
568
+ return []
569
+
570
+
571
+ @dataclass
572
+ class TpySetLiteral(TpyExpr):
573
+ """Set literal: {value, value, ...}"""
574
+ elements: list[TpyExpr]
575
+
576
+ def children(self) -> list[TpyExpr]:
577
+ return list(self.elements)
578
+
579
+
580
+ @dataclass
581
+ class TpySlice(TpyExpr):
582
+ """Slice expression: lower:upper or lower:upper:step."""
583
+ lower: TpyExpr | None = None
584
+ upper: TpyExpr | None = None
585
+ step: TpyExpr | None = None # reserved for future step support
586
+
587
+ def children(self) -> list[TpyExpr]:
588
+ return [x for x in (self.lower, self.upper, self.step) if x is not None]
589
+
590
+
591
+ @dataclass
592
+ class TpySubscript(TpyExpr):
593
+ """Subscript indexing: obj[index]"""
594
+ obj: TpyExpr
595
+ index: TpyExpr # TpySlice for slicing, other TpyExpr for single-index
596
+ needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
597
+ enum_from_name: 'NominalType | None' = None # Set by sema for Color["Red"] name lookup
598
+ bounds_safe: bool = False # Set by sema: index provably in [0, len(obj)), skip bounds check
599
+ is_stepped_slice: bool = False # Set by sema: slice has step (a[::2])
600
+ slice_function_info: 'FunctionInfo | None' = None # Set by sema: resolved __getitem__ for slice
601
+ typed_dict_field: str | None = None # Set by sema: d["key"] on TypedDict -> field access
602
+ typed_dict_optional: bool = False # Set by sema: total=False field, needs runtime check
603
+
604
+ def children(self) -> list[TpyExpr]:
605
+ return [self.obj, self.index]
606
+
607
+
608
+ @dataclass
609
+ class TpyCoerce(TpyExpr):
610
+ """Expression with an explicit coercion attached by semantic analysis."""
611
+ expr: TpyExpr
612
+ actual_type: TpyType
613
+ expected_type: TpyType
614
+ coercion: "Coercion"
615
+ context_kind: str
616
+ context_msg: str
617
+ runtime_bigint: bool = False
618
+
619
+ def children(self) -> list[TpyExpr]:
620
+ return [self.expr]
621
+
622
+
623
+ @dataclass
624
+ class TpyIfExpr(TpyExpr):
625
+ """Ternary conditional expression: then_expr if condition else else_expr."""
626
+ condition: TpyExpr
627
+ then_expr: TpyExpr
628
+ else_expr: TpyExpr
629
+
630
+ def children(self) -> list[TpyExpr]:
631
+ return [self.condition, self.then_expr, self.else_expr]
632
+
633
+
634
+ @dataclass
635
+ class TpyNamedExpr(TpyExpr):
636
+ """Walrus operator: (x := expr)."""
637
+ target: str
638
+ value: TpyExpr
639
+
640
+ def children(self) -> list[TpyExpr]:
641
+ return [self.value]
642
+
643
+
644
+ @dataclass
645
+ class TpyAwait(TpyExpr):
646
+ """`await x` expression. Lowered by PR 3 (resumable-frame codegen)
647
+ to a poll-and-park sequence on the operand's Awaitable[T] conformance.
648
+ Sema rejects awaits outside an async def body.
649
+
650
+ Sema attaches one of two resolution shapes:
651
+ - `awaited_async_func_name`: inline mode -- the operand is a direct
652
+ call to a known async def. Codegen emits the sub-coroutine struct
653
+ as a frame field via `std::optional<__<name>Coro>` and constructs
654
+ it in place from the call's args.
655
+ For async methods, `awaited_method_owner_type` carries the
656
+ receiver's NominalType (including its type args) so the sub-coro
657
+ struct can be uniquely named (`__coro_<Record>_<method>`) and
658
+ qualified with the receiver's class-level template args.
659
+ - `awaited_task_inner`: erased mode -- the operand has type
660
+ `Task[T]`. Codegen emits the sub-future field as
661
+ `std::optional<::tpy::Task<T>>` and moves the operand value in.
662
+ Exactly one is set on a successfully-analyzed TpyAwait.
663
+
664
+ For generic async defs (`async def f[T](...) -> T`), codegen reads
665
+ the inferred type args off the operand call node directly
666
+ (`value.inferred_type_args`) to qualify the sub-coro struct name as
667
+ `__coro_<name><A, B>`. The await expression's substituted result
668
+ type is recorded via the standard expression-type table.
669
+
670
+ `suspension_index` is the ordinal (in source order) within the
671
+ enclosing async def, used to name __sub_<i> fields and
672
+ S_AFTER_AWAIT_<i> states.
673
+ """
674
+ value: TpyExpr
675
+ awaited_async_func_name: str | None = field(default=None, kw_only=True)
676
+ awaited_method_owner_type: 'NominalType | None' = field(default=None, kw_only=True)
677
+ awaited_task_inner: 'TpyType | None' = field(default=None, kw_only=True)
678
+ suspension_index: int | None = field(default=None, kw_only=True)
679
+
680
+ def children(self) -> list[TpyExpr]:
681
+ return [self.value]
682
+
683
+
684
+ @dataclass
685
+ class TpyStmt:
686
+ """Base class for statements."""
687
+ loc: SourceLocation | None = field(default=None, kw_only=True)
688
+
689
+ def exprs(self) -> list[TpyExpr]:
690
+ """Return direct child expressions for generic tree walking."""
691
+ return []
692
+
693
+ def sub_bodies(self) -> list[list[TpyStmt]]:
694
+ """Return sub-statement bodies for recursive walking."""
695
+ return []
696
+
697
+
698
+ class VarLinkage(Enum):
699
+ """Linkage mode for global variable imports."""
700
+ DEFAULT = "default"
701
+ NATIVE = "native" # C++ global import
702
+ NATIVE_C = "native_c" # C global import (extern "C")
703
+ NATIVE_C_ARRAY = "native_c_array" # C array global (extern "C" T name[])
704
+
705
+
706
+ @dataclass
707
+ class TpyVarDecl(TpyStmt):
708
+ """Variable declaration with optional initializer.
709
+
710
+ `type` may temporarily hold a TypeRefNode when emitted by the parser at
711
+ annotation sites; the sema writer (`_analyze_var_decl`) calls
712
+ `TypeOperations.resolve_type_ref` to produce a TpyType and writes it
713
+ back in place. All downstream sema/codegen reads see only TpyType.
714
+ """
715
+ name: str
716
+ type: Optional[TpyType | TypeRefNode]
717
+ init: Optional[TpyExpr]
718
+ linkage: VarLinkage = VarLinkage.DEFAULT
719
+ native_name: str | None = None
720
+ # TODO: is_final (and linkage) could be generalized into a modifiers set
721
+ # (e.g. modifiers: set[str]) to avoid per-feature boolean fields
722
+ is_final: bool = False # Set by sema for Final[T] constant globals
723
+ # Set by sema: union assignment narrowing facts for codegen
724
+ then_type_facts: dict[str, TpyType] = field(default_factory=dict)
725
+
726
+ def exprs(self) -> list[TpyExpr]:
727
+ return [self.init] if self.init else []
728
+
729
+
730
+ @dataclass
731
+ class TpyTupleUnpack(TpyStmt):
732
+ """Tuple unpacking: a, b = expr. None in targets means discard (_)."""
733
+ targets: list[str | None]
734
+ value: TpyExpr
735
+ # Set by sema:
736
+ target_types: list[TpyType] = field(default_factory=list)
737
+ is_new: list[bool] = field(default_factory=list)
738
+ is_owned: list[bool] = field(default_factory=list)
739
+ is_ref: list[bool] = field(default_factory=list)
740
+ is_const_ref: list[bool] = field(default_factory=list)
741
+
742
+ def exprs(self) -> list[TpyExpr]:
743
+ return [self.value]
744
+
745
+
746
+ @dataclass
747
+ class TpyAssign(TpyStmt):
748
+ """Assignment to variable or field."""
749
+ target: TpyExpr
750
+ value: TpyExpr
751
+
752
+ def exprs(self) -> list[TpyExpr]:
753
+ return [self.target, self.value]
754
+
755
+
756
+ @dataclass
757
+ class TpyAugAssign(TpyStmt):
758
+ """Augmented assignment (+=, -=, etc.)"""
759
+ target: TpyExpr
760
+ op: str
761
+ value: TpyExpr
762
+ resolved_binop: 'ResolvedBinop | None' = None # Set by sema for builtin ops
763
+ resolved_inplace: 'ResolvedBinop | None' = None # Set by sema for in-place ops (__iadd__, __ior__, etc.)
764
+
765
+ def exprs(self) -> list[TpyExpr]:
766
+ return [self.target, self.value]
767
+
768
+
769
+ @dataclass
770
+ class TpyDelItem(TpyStmt):
771
+ """Delete statement: del obj[key], obj2[key2], ..."""
772
+ targets: list[TpySubscript]
773
+
774
+ def exprs(self) -> list[TpyExpr]:
775
+ return list(self.targets)
776
+
777
+
778
+ @dataclass
779
+ class TpyDelVar(TpyStmt):
780
+ """Delete statement for local variables: del x, y, ..."""
781
+ names: list[str]
782
+
783
+ def exprs(self) -> list[TpyExpr]:
784
+ return []
785
+
786
+
787
+ @dataclass
788
+ class TpyDelAttr(TpyStmt):
789
+ """Delete statement for attributes: del obj.foo, obj2.bar, ... (D16 Phase 3)"""
790
+ targets: list[TpyFieldAccess]
791
+
792
+ def exprs(self) -> list[TpyExpr]:
793
+ return list(self.targets)
794
+
795
+
796
+ @dataclass
797
+ class TpyExprStmt(TpyStmt):
798
+ """Expression statement (e.g., function call)."""
799
+ expr: TpyExpr
800
+
801
+ def exprs(self) -> list[TpyExpr]:
802
+ return [self.expr]
803
+
804
+
805
+ @dataclass
806
+ class TpyReturn(TpyStmt):
807
+ """Return statement."""
808
+ value: Optional[TpyExpr]
809
+ # Set by sema: the analyzed type of the return expression (before coercion)
810
+ value_type: Optional['TpyType'] = None
811
+
812
+ def exprs(self) -> list[TpyExpr]:
813
+ return [self.value] if self.value else []
814
+
815
+
816
+ @dataclass
817
+ class TpyYield(TpyStmt):
818
+ """Yield statement in a generator function."""
819
+ value: TpyExpr
820
+
821
+ def exprs(self) -> list[TpyExpr]:
822
+ return [self.value]
823
+
824
+
825
+ @dataclass
826
+ class TpyAssert(TpyStmt):
827
+ """Assert statement."""
828
+ condition: TpyExpr
829
+ message: TpyExpr | None = None
830
+ # Set by sema: isinstance union narrowing facts that hold after a passing assert
831
+ then_type_facts: dict[str, TpyType] = field(default_factory=dict)
832
+
833
+ def exprs(self) -> list[TpyExpr]:
834
+ result = [self.condition]
835
+ if self.message:
836
+ result.append(self.message)
837
+ return result
838
+
839
+
840
+ @dataclass
841
+ class TpyIf(TpyStmt):
842
+ """If statement."""
843
+ condition: TpyExpr
844
+ then_body: list[TpyStmt]
845
+ else_body: list[TpyStmt]
846
+ # Set by sema: isinstance union narrowing facts for codegen
847
+ then_type_facts: dict[str, TpyType] = field(default_factory=dict)
848
+ else_type_facts: dict[str, TpyType] = field(default_factory=dict)
849
+
850
+ def exprs(self) -> list[TpyExpr]:
851
+ return [self.condition]
852
+
853
+ def sub_bodies(self) -> list[list[TpyStmt]]:
854
+ return [self.then_body, self.else_body]
855
+
856
+
857
+ @dataclass
858
+ class TpyWhile(TpyStmt):
859
+ """While loop."""
860
+ condition: TpyExpr
861
+ body: list[TpyStmt]
862
+ orelse: list[TpyStmt] = field(default_factory=list)
863
+ # Set by sema: isinstance union narrowing facts for codegen
864
+ then_type_facts: dict[str, TpyType] = field(default_factory=dict)
865
+
866
+ def exprs(self) -> list[TpyExpr]:
867
+ return [self.condition]
868
+
869
+ def sub_bodies(self) -> list[list[TpyStmt]]:
870
+ return [self.body, self.orelse]
871
+
872
+
873
+ @dataclass
874
+ class TpyForEach(TpyStmt):
875
+ """For-each loop over a collection."""
876
+ var: str
877
+ iterable: TpyExpr
878
+ body: list[TpyStmt]
879
+ orelse: list[TpyStmt] = field(default_factory=list)
880
+ enum_iterable: 'NominalType | None' = None # set by sema when iterating over enum type
881
+ elem_type: 'TpyType | None' = None # set by sema: resolved element type for codegen
882
+ is_tuple_unpack: bool = False # set by parser: synthetic loop var for tuple destructuring
883
+ const_loop_var: bool = False # set by sema: loop var is never mutated, safe for const auto&
884
+ hoist_loop_var: bool = False # set by sema: loop var used after loop, needs pre-declaration
885
+ consuming_iter_fi: 'FunctionInfo | None' = None # set by sema: consuming __iter__ overload at last use
886
+ is_async: bool = False # Set by parser: `async for` (lowers to __aiter__/await __anext__ inside async def)
887
+ async_aiter_type: 'NominalType | None' = None # set by sema for async-for: resolved aiter record (return of __aiter__())
888
+
889
+ def exprs(self) -> list[TpyExpr]:
890
+ return [self.iterable]
891
+
892
+ def sub_bodies(self) -> list[list[TpyStmt]]:
893
+ return [self.body, self.orelse]
894
+
895
+
896
+ @dataclass
897
+ class TpyBreak(TpyStmt):
898
+ """Break statement."""
899
+ pass
900
+
901
+
902
+ @dataclass
903
+ class TpyContinue(TpyStmt):
904
+ """Continue statement."""
905
+ pass
906
+
907
+
908
+ @dataclass
909
+ class TpyPassStmt(TpyStmt):
910
+ """Pass statement (no-op)."""
911
+ pass
912
+
913
+
914
+ @dataclass
915
+ class TpyGlobal(TpyStmt):
916
+ """global x, y -- declares names as referring to module-level variables."""
917
+ names: list[str]
918
+
919
+
920
+ @dataclass
921
+ class TpyNonlocal(TpyStmt):
922
+ """nonlocal x, y -- declares names as mutable captures from enclosing scope."""
923
+ names: list[str]
924
+
925
+
926
+ @dataclass
927
+ class TpyRaise(TpyStmt):
928
+ """raise E, raise E(args), raise <expr>, or bare raise (re-raise in except block)."""
929
+ exception_type: str | None = None # type-constructor form (set by parser, qualified by sema)
930
+ args: list[TpyExpr] = field(default_factory=list) # constructor arguments
931
+ raise_expr: TpyExpr | None = None # expression form: raise <expr> (throw-tier only)
932
+ is_call_form: bool = False # raise Name() vs raise Name (set by parser)
933
+ deref_depth: int = 0 # Set by sema: __deref__ steps to peel before calling __raise__ (Phase 20)
934
+
935
+ def exprs(self) -> list[TpyExpr]:
936
+ result = list(self.args)
937
+ if self.raise_expr is not None:
938
+ result.append(self.raise_expr)
939
+ return result
940
+
941
+
942
+ @dataclass
943
+ class TpyExceptHandler:
944
+ """A single except clause in a try statement."""
945
+ exception_type: str | None # None for bare except:
946
+ binding: str | None # from "as e"
947
+ body: list[TpyStmt]
948
+ loc: SourceLocation | None = None
949
+
950
+
951
+ @dataclass
952
+ class TpyTry(TpyStmt):
953
+ """try/except/else/finally statement (both return-tier and throw-tier)."""
954
+ try_body: list[TpyStmt]
955
+ handlers: list[TpyExceptHandler] # 0+ except clauses
956
+ else_body: list[TpyStmt] # may be empty
957
+ finally_body: list[TpyStmt] # may be empty
958
+ # Set by sema: "return" for ReturnException goto-based, "throw" for C++ try/catch
959
+ tier: Literal["return", "throw", "finally_only"] | None = None
960
+
961
+ def sub_bodies(self) -> list[list[TpyStmt]]:
962
+ bodies = [self.try_body, self.else_body, self.finally_body]
963
+ for h in self.handlers:
964
+ bodies.append(h.body)
965
+ return bodies
966
+
967
+
968
+ @dataclass
969
+ class TpyWithItem:
970
+ """A single context manager in a with statement."""
971
+ context_expr: TpyExpr
972
+ target: str | None # as-variable name, or None if no `as`
973
+ loc: SourceLocation | None = None
974
+ # Set by sema: type returned by __enter__()
975
+ enter_type: TpyType | None = None
976
+ # Set by sema: True iff this ctx-manager's __exit__ returns bool (i.e.
977
+ # can suppress exceptions). False for None-returning __exit__.
978
+ exit_can_suppress: bool = False
979
+ # Set by sema: True iff exc_val is typed as Optional[BaseException]
980
+ # (i.e. the body inspects exceptions). False when exc_val: None.
981
+ # Codegen call site passes &__exc / nullptr when True, else {}.
982
+ exit_takes_exc_val: bool = False
983
+
984
+
985
+ @dataclass
986
+ class TpyWith(TpyStmt):
987
+ """with statement (context managers).
988
+
989
+ `is_async=True` when this came from Python `async with`; the
990
+ context managers' `__aenter__` / `__aexit__` are async methods and
991
+ each call is a suspension point. Only allowed inside `async def`.
992
+ """
993
+ items: list[TpyWithItem]
994
+ body: list[TpyStmt]
995
+ is_async: bool = False
996
+
997
+ def exprs(self) -> list[TpyExpr]:
998
+ return [item.context_expr for item in self.items]
999
+
1000
+ def sub_bodies(self) -> list[list[TpyStmt]]:
1001
+ return [self.body]
1002
+
1003
+
1004
+ @dataclass
1005
+ class TpyNestedDef(TpyStmt):
1006
+ """Nested function definition inside a function body.
1007
+
1008
+ Compiles to a C++ lambda assigned to a local auto variable.
1009
+ Capture analysis is performed by sema.
1010
+ """
1011
+ func: 'TpyFunction'
1012
+ # Set by sema:
1013
+ captured_names: list[str] = field(default_factory=list)
1014
+ nonlocal_names: set[str] = field(default_factory=set)
1015
+ escapes: bool = False
1016
+ # Captures that should use by-reference even in escaping closures
1017
+ # (non-value outer params whose original object outlives the closure)
1018
+ ref_captures: set[str] = field(default_factory=set)
1019
+ # Non-value locals to move into the closure (last use, no copy needed)
1020
+ move_captures: set[str] = field(default_factory=set)
1021
+
1022
+
1023
+ # -- Pattern matching nodes --
1024
+
1025
+ @dataclass
1026
+ class TpyPattern:
1027
+ """Base class for match/case patterns."""
1028
+ loc: SourceLocation | None = field(default=None, kw_only=True)
1029
+
1030
+
1031
+ @dataclass
1032
+ class TpyWildcardPattern(TpyPattern):
1033
+ """case _:"""
1034
+ pass
1035
+
1036
+
1037
+ @dataclass
1038
+ class TpyCapturePattern(TpyPattern):
1039
+ """case x:"""
1040
+ name: str
1041
+
1042
+
1043
+ @dataclass
1044
+ class TpyClassPattern(TpyPattern):
1045
+ """case Circle(): / case Circle(radius=r):"""
1046
+ cls: TpyExpr
1047
+ positional: list[TpyPattern]
1048
+ keywords: list[tuple[str, TpyPattern]]
1049
+ # Set by sema: resolved record type for this class pattern
1050
+ resolved_type: TpyType | None = None
1051
+ # Set by sema: True when this is a field sub-pattern matching against
1052
+ # a union-typed field (codegen emits holds_alternative + std::get)
1053
+ is_union_field_guard: bool = False
1054
+
1055
+
1056
+ @dataclass
1057
+ class TpyLiteralPattern(TpyPattern):
1058
+ """case 42: / case "hello": / case True: / case None:"""
1059
+ value: int | float | str | bool | None
1060
+
1061
+
1062
+ @dataclass
1063
+ class TpyValuePattern(TpyPattern):
1064
+ """case Color.RED: -- named constant via attribute access"""
1065
+ expr: TpyExpr
1066
+
1067
+
1068
+ @dataclass
1069
+ class TpyOrPattern(TpyPattern):
1070
+ """case Dog() | Cat():"""
1071
+ patterns: list[TpyPattern]
1072
+
1073
+
1074
+ @dataclass
1075
+ class TpyAsPattern(TpyPattern):
1076
+ """case P() as x:"""
1077
+ pattern: TpyPattern
1078
+ name: str
1079
+
1080
+
1081
+ @dataclass
1082
+ class TpyMatchCase:
1083
+ """A single case arm in a match statement."""
1084
+ pattern: TpyPattern
1085
+ guard: TpyExpr | None
1086
+ body: list[TpyStmt]
1087
+ loc: SourceLocation | None = None
1088
+ # Set by sema: narrowing facts for the subject variable in this arm
1089
+ type_facts: dict[str, TpyType] = field(default_factory=dict)
1090
+
1091
+
1092
+ @dataclass
1093
+ class TpyMatch(TpyStmt):
1094
+ """match/case statement."""
1095
+ subject: TpyExpr
1096
+ cases: list[TpyMatchCase]
1097
+ # Set by sema: resolved type of the subject expression
1098
+ subject_type: TpyType | None = None
1099
+ # Set by sema: true when the match covers every possible subject value
1100
+ # (either via a wildcard arm or by enumerating every value of a finite
1101
+ # type). Codegen relies on this to emit ``std::unreachable()`` past the
1102
+ # match end-label so non-void functions whose only exit is the match
1103
+ # don't trip -Wreturn-type.
1104
+ is_exhaustive: bool = False
1105
+
1106
+ def exprs(self) -> list[TpyExpr]:
1107
+ result: list[TpyExpr] = [self.subject]
1108
+ for case in self.cases:
1109
+ if case.guard is not None:
1110
+ result.append(case.guard)
1111
+ return result
1112
+
1113
+ def sub_bodies(self) -> list[list[TpyStmt]]:
1114
+ return [case.body for case in self.cases]
1115
+
1116
+
1117
+ @dataclass(frozen=True)
1118
+ class RelativeImportKey:
1119
+ """Structured key for relative import placeholders in import dicts.
1120
+
1121
+ Used as a temporary dict key before relative imports are resolved to
1122
+ canonical module names during discovery.
1123
+ """
1124
+ # Prefix uses \x00 which cannot appear in a Python identifier,
1125
+ # making collision with real module names structurally impossible.
1126
+ _PREFIX = "\x00rel:"
1127
+
1128
+ level: int
1129
+ line: int
1130
+ col: int
1131
+ partial: str # Module name after dots ("" for bare "from . import X")
1132
+
1133
+ def encode(self) -> str:
1134
+ """Encode as a unique string key for use in dicts."""
1135
+ return f"{self._PREFIX}{self.level}:{self.line}_{self.col}:{self.partial}"
1136
+
1137
+ @staticmethod
1138
+ def decode(key: str) -> 'RelativeImportKey':
1139
+ """Decode a placeholder string back into structured fields."""
1140
+ # Format: \x00rel:{level}:{line}_{col}:{partial}
1141
+ body = key[len(RelativeImportKey._PREFIX):]
1142
+ level_str, loc, partial = body.split(":", 2)
1143
+ line_str, col_str = loc.split("_", 1)
1144
+ return RelativeImportKey(level=int(level_str), line=int(line_str), col=int(col_str), partial=partial)
1145
+
1146
+ @staticmethod
1147
+ def is_placeholder(key: str) -> bool:
1148
+ """Check if a string key is a relative import placeholder."""
1149
+ return key.startswith(RelativeImportKey._PREFIX)
1150
+
1151
+
1152
+ @dataclass
1153
+ class TpyImport(TpyStmt):
1154
+ """Import statement for user modules.
1155
+
1156
+ Only user module imports (not builtins like tpy, typing) become TpyImport nodes.
1157
+ These are emitted as __tpy_init() calls in codegen.
1158
+
1159
+ For relative imports:
1160
+ - level: Number of dots (0=absolute, 1=".", 2="..", etc.)
1161
+ - relative_name: Original module name after dots (None for "from . import X")
1162
+ - module_name: Initially a RelativeImportKey.encode() placeholder, resolved during discovery
1163
+
1164
+ For aliased imports (import X as Y):
1165
+ - alias: The local name (Y) if different from module_name
1166
+ """
1167
+ module_name: str
1168
+ level: int = 0
1169
+ relative_name: str | None = None
1170
+ alias: str | None = None
1171
+
1172
+
1173
+ class RecordLinkage(Enum):
1174
+ """Linkage mode for records (classes)."""
1175
+ DEFAULT = "default"
1176
+ NATIVE = "native" # C++ class import (fields only)
1177
+ NATIVE_C = "native_c" # C struct import (fields only)
1178
+
1179
+
1180
+ class FunctionLinkage(Enum):
1181
+ """Linkage mode for functions."""
1182
+ DEFAULT = "default"
1183
+ NATIVE = "native" # C++ import (stub, no body)
1184
+ NATIVE_C = "native_c" # C import (stub, no body)
1185
+ EXPORT_C = "export_c" # C export (has body)
1186
+
1187
+
1188
+ @dataclass
1189
+ class TpyFunction:
1190
+ """Function definition.
1191
+
1192
+ For generic functions like def first[T](items: list[T]) -> T:
1193
+ - type_params stores the type parameter names (e.g., ["T"])
1194
+ - type_param_bounds stores bounds for each bounded type param (e.g., {"T": Comparable})
1195
+ """
1196
+ name: str
1197
+ # Between parse and the post-parse resolve_refs pass, params
1198
+ # and return_type for top-level (non-method) functions may hold
1199
+ # TypeRefNode in place of TpyType. All readers post-pre-pass see
1200
+ # TpyType. `return_type` may also be None when no annotation was
1201
+ # provided -- sema substitutes VOID during the pre-pass.
1202
+ params: list[tuple[str, TpyType | TypeRefNode]]
1203
+ return_type: 'TpyType | TypeRefNode | None'
1204
+ body: list[TpyStmt]
1205
+ is_noalloc: bool = False
1206
+ is_inline: bool = False
1207
+ is_readonly: bool = False
1208
+ readonly_opt_out: bool = False
1209
+ is_pure: bool = False
1210
+ is_override: bool = False
1211
+ hides_parent: bool = False
1212
+ is_overload_stub: bool = False
1213
+ is_method: bool = False
1214
+ is_staticmethod: bool = False
1215
+ is_property_getter: bool = False
1216
+ is_property_setter: bool = False
1217
+ property_name: str | None = None # for setter: which property it belongs to
1218
+ is_consuming: bool = False
1219
+ # Transient input to sema.method_expansion._clone_auto_readonly:
1220
+ # true for @auto_readonly methods, @property getters, methods with
1221
+ # `self: auto_readonly[Self]`, and methods with per-param
1222
+ # `auto_readonly[T]`. After cloning, both clones carry False.
1223
+ auto_readonly: bool = False
1224
+ # Set on the mutable clone produced by
1225
+ # sema.method_expansion._clone_auto_readonly; used to detect
1226
+ # mutable+const clone pairs without relying on params-list identity.
1227
+ is_auto_readonly_mutable_clone: bool = False
1228
+ # Set on both clones after _clone_auto_readonly resolves AutoReadonlyType
1229
+ # in params. Tells sema/codegen not to blanket-apply readonly to all params
1230
+ # (each param already carries ReadonlyType or not from the clone).
1231
+ auto_readonly_params_resolved: bool = False
1232
+ # Transient input to sema.method_expansion._clone_auto_own: true when
1233
+ # `self: auto_own[Self]` is detected. After cloning, both clones have
1234
+ # auto_own=False.
1235
+ auto_own: bool = False
1236
+ # Set on the borrowing clone produced by
1237
+ # sema.method_expansion._clone_auto_own.
1238
+ is_auto_own_borrowing_clone: bool = False
1239
+ linkage: FunctionLinkage = FunctionLinkage.DEFAULT
1240
+ native_name: str | None = None
1241
+ native_function: bool = False
1242
+ native_preserves_refs: bool = False
1243
+ # @native(cpp_return_type=T) -- see FunctionInfo.native_cpp_return_type.
1244
+ native_cpp_return_type: str | None = None
1245
+ cpp_template: str | None = None
1246
+ is_stub: bool = False
1247
+ value_ptr_coercion: bool = False
1248
+ type_params: list[str] = field(default_factory=list)
1249
+ # Parallel with type_params (like TpyRecord.type_param_kinds). Carries
1250
+ # TypeParamKind.INT for `def f[N: int](...)` so sema-time resolution
1251
+ # of ref-based params can tell INT-kind type params from TYPE-kind.
1252
+ type_param_kinds: list[TypeParamKind] = field(default_factory=list)
1253
+ # Between parse and the post-parse resolve_refs pass, bounds
1254
+ # may hold TypeRefNode in place of TpyType. All readers post-pre-pass
1255
+ # see TpyType.
1256
+ type_param_bounds: 'dict[str, TpyType | TypeRefNode]' = field(default_factory=dict)
1257
+ type_param_defaults: dict[str, str] = field(default_factory=dict) # e.g. {"T": "tpy.extern.DefaultInt"}
1258
+ defaults: list['TpyExpr | None'] = field(default_factory=list) # len == len(params); None = no default
1259
+ keyword_only_start: int | None = None # index into params where keyword-only begins
1260
+ vararg_name: str | None = None # name of *args parameter
1261
+ vararg_type: 'TpyType | TypeRefNode | None' = None # element type T from *args: T
1262
+ kwarg_name: str | None = None # name of **kwargs parameter
1263
+ # Between parse and the post-parse resolve_refs pass, kwarg_type
1264
+ # may hold TypeRefNode in place of TpyType.
1265
+ kwarg_type: 'TpyType | TypeRefNode | None' = None # TypedDict type from **kwargs: Unpack[TD]
1266
+ error_return: str | None = None # @error_return(E) exception type name
1267
+ builtin_decorator_key: str | None = None # @builtin_decorator("tpy.readonly")
1268
+ builtin_function_key: str | None = None # @builtin_function("tpy.extern.native_global")
1269
+ is_generator: bool = False # Set by parser: body contains yield
1270
+ generator_yield_type: 'TpyType | None' = None # Set by sema: T from Iterator[T]
1271
+ generator_locals: 'list[tuple[str, TpyType]] | None' = None # Set by sema: local vars for struct fields
1272
+ is_async: bool = False # Set by parser: `async def`. Lowered to a state-machine
1273
+ # struct conforming to Awaitable[T] in PR 3 (codegen).
1274
+ # Set by `ClassInfo.add_method` for any function added through the macro
1275
+ # API. Lets sema diagnostics distinguish synthesized methods (e.g.
1276
+ # @dataclass __init__) from user-written ones, so messages can point the
1277
+ # user at the macro/decorator rather than at code they didn't write.
1278
+ is_macro_generated: bool = False
1279
+ # Optional name of the macro/decorator that produced this function
1280
+ # (e.g. "dataclass", "model", "total_ordering"). Set by macro modules
1281
+ # when calling `ClassInfo.add_method(..., macro_origin=...)`. Used by
1282
+ # diagnostics to name the responsible decorator in user-facing messages.
1283
+ macro_origin: str | None = None
1284
+ skip_codegen: bool = False # Set by sema: @inline function, body inlined at call sites
1285
+ # Preserves the self annotation for methods (e.g. OwnType(SelfType),
1286
+ # AutoOwnType(SelfType), AutoReadonlyType(SelfType)). Between parse
1287
+ # and the post-parse resolve_refs pass this may hold a
1288
+ # TypeRefNode; sema resolves before `method_expansion` reads it.
1289
+ # None when self had no explicit annotation or for non-methods.
1290
+ # Carried through `dc_replace` on auto_own / auto_readonly clones.
1291
+ self_annotation: 'TpyType | TypeRefNode | None' = None
1292
+ # Set by the parser when the method carries `@auto_readonly`.
1293
+ # `method_expansion` uses this to drive AutoReadonlyType param
1294
+ # wrapping; cleared on the auto_readonly clones once expanded.
1295
+ has_auto_readonly_decorator: bool = False
1296
+ loc: SourceLocation | None = None
1297
+
1298
+ @property
1299
+ def has_implementation(self) -> bool:
1300
+ """True when this function/stub has its own implementation.
1301
+
1302
+ A function has an implementation if it carries a body, maps to an
1303
+ external C++ symbol (@native), or expands via a C++ template
1304
+ (@cpp_template). Plain bodyless stubs (``def f(x: int) -> int: ...``)
1305
+ are declaration-only and need a separate implementation.
1306
+ """
1307
+ if not self.is_stub:
1308
+ return True
1309
+ return self.linkage != FunctionLinkage.DEFAULT or self.cpp_template is not None
1310
+
1311
+ @property
1312
+ def is_extern_c(self) -> bool:
1313
+ return self.linkage == FunctionLinkage.EXPORT_C
1314
+
1315
+ @property
1316
+ def is_export(self) -> bool:
1317
+ return self.linkage == FunctionLinkage.EXPORT_C
1318
+
1319
+ @property
1320
+ def extern_name(self) -> str | None:
1321
+ return self.native_name
1322
+
1323
+
1324
+ @dataclass
1325
+ class TpyRecord:
1326
+ """Record (class) definition.
1327
+
1328
+ For generic records like Stack[T]:
1329
+ - type_params stores the type parameter names (e.g., ["T"])
1330
+ - type_param_kinds stores the kind of each type param (TYPE or INT)
1331
+ - type_param_bounds stores bounds for each bounded type param (e.g., {"T": Comparable})
1332
+
1333
+ For generic records with integer type params like Matrix[T, N: int]:
1334
+ - type_params = ["T", "N"]
1335
+ - type_param_kinds = [TYPE, INT]
1336
+
1337
+ For class inheritance:
1338
+ - bases stores the parsed base types (classes or protocols)
1339
+ - Classification into parent class vs protocol implementations is done in sema
1340
+ """
1341
+ name: str
1342
+ fields: list[FieldInfo]
1343
+ methods: list[TpyFunction] = field(default_factory=list)
1344
+ type_params: list[str] = field(default_factory=list)
1345
+ type_param_kinds: list[TypeParamKind] = field(default_factory=list)
1346
+ # Between parse and the post-parse resolve_refs pass, bounds
1347
+ # may hold TypeRefNode in place of TpyType. All readers post-pre-pass
1348
+ # see TpyType.
1349
+ type_param_bounds: 'dict[str, TpyType | TypeRefNode]' = field(default_factory=dict)
1350
+ # Between parse and the post-parse resolve_refs pass, bases
1351
+ # may hold TypeRefNode in place of TpyType. All readers post-pre-
1352
+ # pass see TpyType.
1353
+ bases: 'list[TpyType | TypeRefNode]' = field(default_factory=list)
1354
+ linkage: RecordLinkage = RecordLinkage.DEFAULT
1355
+ native_name: str | None = None
1356
+ is_nocopy: bool = False
1357
+ is_frozen: bool = False
1358
+ is_typed_dict: bool = False
1359
+ is_total_false: bool = False # TypedDict(total=False): all fields Optional
1360
+ builtin_type_key: str | None = None
1361
+ # @native(indirecting=True): record owns indirect (heap-backed) storage
1362
+ # of its type parameter that the compiler cannot introspect (e.g. list/
1363
+ # dict/set, or a user @native record wrapping a C++ unique_ptr).
1364
+ # Consulted by cycle detection so the type breaks recursive size cycles.
1365
+ # User TPy records with a Ptr[T] field do NOT set this -- the field walk
1366
+ # infers indirection structurally.
1367
+ is_indirecting: bool = False
1368
+ pending_macros: list[tuple[str, dict[str, Any]]] = field(default_factory=list)
1369
+ # Callbacks registered via `ClassInfo.defer_until_macros_complete()`.
1370
+ # Run after the eager macro pass so a macro can inspect the final
1371
+ # method set produced by composition (e.g. @total_ordering picking
1372
+ # an anchor among ordering ops other macros may add).
1373
+ pending_deferred_macros: list[Any] = field(default_factory=list)
1374
+ nested_records: list[TpyRecord] = field(default_factory=list)
1375
+ nested_enums: list[TpyEnum] = field(default_factory=list)
1376
+ loc: SourceLocation | None = None
1377
+
1378
+ @property
1379
+ def init_method(self) -> Optional[TpyFunction]:
1380
+ """Get __init__ method if present."""
1381
+ for m in self.methods:
1382
+ if m.name == "__init__":
1383
+ return m
1384
+ return None
1385
+
1386
+ @property
1387
+ def del_method(self) -> Optional[TpyFunction]:
1388
+ """Get __del__ method if present."""
1389
+ for m in self.methods:
1390
+ if m.name == "__del__":
1391
+ return m
1392
+ return None
1393
+
1394
+
1395
+ @dataclass
1396
+ class TpyProtocol:
1397
+ """Protocol definition for structural subtyping."""
1398
+ name: str
1399
+ methods: list[MethodSignature]
1400
+ # Between parse and the post-parse resolve_refs pass, field
1401
+ # types may hold TypeRefNode in place of TpyType.
1402
+ fields: 'list[tuple[str, TpyType | TypeRefNode]]' = field(default_factory=list)
1403
+ type_params: list[str] = field(default_factory=list)
1404
+ # Parent protocols. Parser emits TypeRefNode (TpyTypeRef) entries; sema's
1405
+ # resolve_refs pass replaces them in place with NominalType. Generic
1406
+ # parents like `Iterable[T]` carry their type args in NominalType.type_args.
1407
+ parent_protocols: 'list[NominalType | TypeRefNode]' = field(default_factory=list)
1408
+ is_dynamic: bool = False
1409
+ cpp_concept: str | None = None
1410
+ loc: SourceLocation | None = None
1411
+
1412
+
1413
+ @dataclass
1414
+ class TpyEnum:
1415
+ """Enum definition -- symbolic constants grouped under a named type."""
1416
+ name: str
1417
+ members: list[tuple[str, int, SourceLocation | None]] # auto() already resolved to int by parser
1418
+ is_int_enum: bool = False
1419
+ underlying_type_name: str | None = None # e.g. "int", "Int8", "UInt32"
1420
+ is_native: bool = False
1421
+ native_name: str | None = None # raw @native arg; sema normalizes to canonical ::-prefixed form
1422
+ # Per-member C++ rename map (Python name -> C++ enumerator name). Used
1423
+ # for @native enums where the C++ side has a name TPy can't spell
1424
+ # (Python keywords like `None`) or uses a different convention.
1425
+ # Members without an entry use the Python name as the C++ name.
1426
+ cpp_member_names: dict[str, str] = field(default_factory=dict)
1427
+ # True when the user spelled at least one explicit integer literal as a
1428
+ # member value. For @native enums this triggers per-member static_assert
1429
+ # emission so the TPy-declared value is verified against the C++ side at
1430
+ # compile time. False when all members used auto() / native_member()
1431
+ # (the user opted out of declaring values; C++ is the truth).
1432
+ has_explicit_values: bool = False
1433
+ loc: SourceLocation | None = None
1434
+
1435
+
1436
+ @dataclass
1437
+ class ParseWarning:
1438
+ """A warning generated during parsing."""
1439
+ message: str
1440
+ loc: SourceLocation | None
1441
+
1442
+
1443
+ @dataclass
1444
+ class ModuleDirectives:
1445
+ """Module-level compiler directives from # tpy: comments."""
1446
+ # Each entry: (include_path, platform_filter_or_None)
1447
+ includes: list[tuple[str, str | None]] = field(default_factory=list)
1448
+ # Each entry: (lib_name, platform_filter_or_None)
1449
+ link_libs: list[tuple[str, str | None]] = field(default_factory=list)
1450
+ # Third-party library declarations: (registry_name, platform_filter).
1451
+ # Resolved at build time via tpyc.build.third_party into include paths,
1452
+ # link flags, and CMake snippets based on the user's selected mode.
1453
+ third_party_deps: list[tuple[str, str | None]] = field(default_factory=list)
1454
+ native_module: bool = False
1455
+ # Override C++ namespace (replaces tpyapp::module_name)
1456
+ cpp_namespace: str | None = None
1457
+
1458
+
1459
+ @dataclass
1460
+ class TpyModule:
1461
+ """Top-level module."""
1462
+ records: list[TpyRecord]
1463
+ functions: list[TpyFunction]
1464
+ protocols: list[TpyProtocol] = field(default_factory=list)
1465
+ enums: list[TpyEnum] = field(default_factory=list)
1466
+ top_level_stmts: list[TpyStmt] = field(default_factory=list)
1467
+ source_lines: list[str] = field(default_factory=list) # Original source lines for source mapping
1468
+ # Import tracking: module_name -> set of (original_name, local_name) tuples (for "from X import Y as Z")
1469
+ # module_name -> None (for "import X")
1470
+ imports: dict[str, set[tuple[str, str]] | None] = field(default_factory=dict)
1471
+ # True when "from tpy import *" was used (triggers full tpy registration in sema)
1472
+ tpy_star_import: bool = False
1473
+ # Set of module names that had 'from X import *'
1474
+ star_imports: set[str] = field(default_factory=set)
1475
+ # Module's own `__all__` literal, captured at parse time. `None` when
1476
+ # the module does not define `__all__`; consumers treat that as "no
1477
+ # filter applied beyond the leading-underscore rule".
1478
+ module_all: frozenset[str] | None = None
1479
+ # Source location of the `__all__` literal (the assignment's RHS), used
1480
+ # for diagnostics that point at the `__all__` declaration -- e.g. the
1481
+ # phantom-name warning when `__all__` lists a name the module does not
1482
+ # actually export. None whenever `module_all` is None.
1483
+ module_all_loc: SourceLocation | None = None
1484
+ # Modules to resolve as files: {module_name: line_number}
1485
+ user_module_imports: dict[str, int] = field(default_factory=dict)
1486
+ # Module aliases from "from . import submod" -> {canonical_name: local_name}
1487
+ module_aliases: dict[str, str] = field(default_factory=dict)
1488
+ # Modules that had bare `import X` statements (needed for module binding in sema)
1489
+ bare_module_imports: set[str] = field(default_factory=set)
1490
+ # Type aliases (e.g., Shape = Circle | Rect) -> (resolved type, source
1491
+ # location, type_params, type_param_kinds). `type_params` /
1492
+ # `type_param_kinds` are non-empty only for generic aliases
1493
+ # (`type Tree[T] = ...`); v1 of generic aliases keeps storage
1494
+ # extensible without consuming type_params yet -- see
1495
+ # `docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md`.
1496
+ # Between parse and the post-parse resolve_refs pass, alias
1497
+ # RHS values may hold TypeRefNode in place of TpyType. Sema resolves
1498
+ # each alias passing `pending_alias=alias_name` through the resolver
1499
+ # API so same-body self-refs become NominalType(name) placeholders,
1500
+ # then detects recursive unions post-resolution.
1501
+ type_aliases: 'dict[str, tuple[TpyType | TypeRefNode, SourceLocation | None, list[str], list[TypeParamKind]]]' = field(default_factory=dict)
1502
+ # Parser warnings (e.g., imports after non-import code)
1503
+ parse_warnings: list[ParseWarning] = field(default_factory=list)
1504
+ # Module-level # tpy: directives
1505
+ directives: ModuleDirectives = field(default_factory=ModuleDirectives)
1506
+ # Union type aliases that need wrapper-struct representation (self- or mutually-recursive)
1507
+ recursive_union_names: set[str] = field(default_factory=set)
1508
+ # Parser resolver: a TypeResolver instance attached at end-of-parse
1509
+ # that resolves a TypeRefNode (emitted by the walker at annotation
1510
+ # sites) to a TpyType. Sema invokes `resolver.resolve(ref, scope)`
1511
+ # via `TypeOperations.resolve_type_ref` when a writer encounters an
1512
+ # unresolved ref. Holds a back-reference to the parser so it carries
1513
+ # live resolution state (registry, imports, local_defs,
1514
+ # module_class_names, type_alias_names, reverse_module_aliases,
1515
+ # bare_module_imports).
1516
+ resolver: 'Any | None' = None
1517
+
1518
+ def all_records(self) -> list[TpyRecord]:
1519
+ """All records including nested, in definition order (depth-first)."""
1520
+ result: list[TpyRecord] = []
1521
+ def collect(records: list[TpyRecord]) -> None:
1522
+ for r in records:
1523
+ result.append(r)
1524
+ collect(r.nested_records)
1525
+ collect(self.records)
1526
+ return result
1527
+
1528
+ def all_enums(self) -> list[TpyEnum]:
1529
+ """All enums including those nested inside records, in definition order."""
1530
+ result: list[TpyEnum] = list(self.enums)
1531
+ def collect(records: list[TpyRecord]) -> None:
1532
+ for r in records:
1533
+ result.extend(r.nested_enums)
1534
+ collect(r.nested_records)
1535
+ collect(self.records)
1536
+ return result
1537
+
1538
+
1539
+ def is_docstring(stmt: TpyStmt) -> bool:
1540
+ """True for a bare string-literal expression statement -- i.e. the
1541
+ syntactic form Python recognises as a docstring at the top of a
1542
+ function/method/class/module body.
1543
+ """
1544
+ return isinstance(stmt, TpyExprStmt) and isinstance(stmt.expr, TpyStrLiteral)
1545
+
1546
+
1547
+ def is_super_del_call(stmt: TpyStmt) -> bool:
1548
+ """Check if a statement is a super().__del__() call."""
1549
+ if isinstance(stmt, TpyExprStmt):
1550
+ expr = stmt.expr
1551
+ if isinstance(expr, TpyMethodCall) and expr.method == "__del__":
1552
+ return expr.super_parent_type is not None
1553
+ return False
1554
+
1555
+
1556
+ def is_base_init_call(stmt: TpyStmt) -> bool:
1557
+ """True for super().__init__(...) or BaseN.__init__(self, ...). Both forms
1558
+ belong in the init section and hoist into the C++ member initializer list.
1559
+ """
1560
+ if isinstance(stmt, TpyExprStmt):
1561
+ expr = stmt.expr
1562
+ if isinstance(expr, TpyMethodCall) and expr.method == "__init__":
1563
+ return (expr.super_parent_type is not None
1564
+ or expr.unbound_self_parent_type is not None)
1565
+ return False
1566
+
1567
+
1568
+ def collect_name_refs(expr: TpyExpr) -> set[str]:
1569
+ """Collect all name references in an expression tree.
1570
+
1571
+ Uses TpyExpr.children() for generic traversal. TpyCall.func is a
1572
+ TpyName child, so callable variable names are captured automatically.
1573
+ """
1574
+ names: set[str] = set()
1575
+ stack: list[TpyExpr] = [expr]
1576
+ while stack:
1577
+ node = stack.pop()
1578
+ if isinstance(node, TpyName):
1579
+ names.add(node.name)
1580
+ else:
1581
+ stack.extend(node.children())
1582
+ return names
1583
+
1584
+
1585
+ def expr_reads_self_field(expr: TpyExpr, fields: set[str]) -> bool:
1586
+ """True if `expr` reads `self.X` for any X in `fields`, or makes any
1587
+ `self.method(...)` call while `fields` is non-empty (methods can read
1588
+ arbitrary fields, so we treat them conservatively).
1589
+
1590
+ Intended for ordering checks between an own-field MIL hoist and prior
1591
+ body writes to `self.*`. Not a general-purpose aliasing oracle -- the
1592
+ method-call rule is over-conservative for that one use case.
1593
+
1594
+ See also `expr_contains_self_method_call` -- the method-only variant
1595
+ (no field-name set, filters out static-method calls). Pick that one
1596
+ when you only care whether any instance method on self was invoked.
1597
+ """
1598
+ if not fields:
1599
+ return False
1600
+ stack: list[TpyExpr] = [expr]
1601
+ while stack:
1602
+ node = stack.pop()
1603
+ if isinstance(node, TpyFieldAccess):
1604
+ if isinstance(node.obj, TpyName) and node.obj.name == "self":
1605
+ if node.field in fields:
1606
+ return True
1607
+ elif isinstance(node, TpyMethodCall):
1608
+ if isinstance(node.obj, TpyName) and node.obj.name == "self":
1609
+ return True
1610
+ stack.extend(node.children())
1611
+ return False
1612
+
1613
+
1614
+ def expr_contains_self_method_call(expr: TpyExpr) -> bool:
1615
+ """True if `expr` contains any non-static `self.method(...)` call.
1616
+
1617
+ Used by __init__ analysis to flag method calls in field-init RHS that
1618
+ might observe still-uninitialized fields.
1619
+
1620
+ See also `expr_reads_self_field` -- combines field-read detection
1621
+ (for a caller-supplied set of field names) with method-call
1622
+ detection. Pick that one when you also need to know if specific
1623
+ self.X reads occur, not just method calls.
1624
+ """
1625
+ stack: list[TpyExpr] = [expr]
1626
+ while stack:
1627
+ node = stack.pop()
1628
+ if isinstance(node, TpyMethodCall):
1629
+ if (isinstance(node.obj, TpyName)
1630
+ and node.obj.name == "self"
1631
+ and not node.is_static_call):
1632
+ return True
1633
+ stack.extend(node.children())
1634
+ return False
1635
+
1636
+
1637
+ def collect_top_level_local_names(stmts: list[TpyStmt]) -> set[str]:
1638
+ """Collect names bound by top-level statements in a function body.
1639
+
1640
+ Covers the forms that introduce locals at this scope: `x: T = ...`
1641
+ (TpyVarDecl), `x = ...` (TpyAssign with a bare-name target), and
1642
+ `a, b = ...` (TpyTupleUnpack). Does NOT descend into control flow:
1643
+ names bound inside `if`, `for`, `while`, etc. are not included --
1644
+ callers reasoning about the top-level MIL/init split only need
1645
+ names visible at statement depth 0.
1646
+ """
1647
+ names: set[str] = set()
1648
+ for s in stmts:
1649
+ if isinstance(s, TpyVarDecl):
1650
+ names.add(s.name)
1651
+ elif isinstance(s, TpyAssign) and isinstance(s.target, TpyName):
1652
+ names.add(s.target.name)
1653
+ elif isinstance(s, TpyTupleUnpack):
1654
+ names.update(t for t in s.targets if t is not None)
1655
+ return names
1656
+
1657
+
1658
+ def is_stable_address_lvalue(expr: TpyExpr) -> bool:
1659
+ """True iff `expr` resolves to a stable lvalue: a bare name (local /
1660
+ parameter / frame field) or a chain of field accesses rooted at a
1661
+ name. Subscripts, calls, binops, conditional / comprehension
1662
+ expressions return temporaries whose address would dangle across
1663
+ an async suspension or a stored borrow.
1664
+
1665
+ Stricter than `sema/compatibility.py::is_lvalue`: subscripts
1666
+ (addressable in C++ but yield container-element temporaries
1667
+ across suspensions) and `TpyCoerce` (sema-only wrapper, doesn't
1668
+ reach codegen) are both excluded.
1669
+
1670
+ Used by:
1671
+ * `sema/expressions.py::analyze_await` to reject
1672
+ `await rvalue.method()` (M4 async-method receiver capture).
1673
+ * `codegen_cpp/gen_async.py::_make_await_payload` to choose
1674
+ BORROWED vs ERASED await mode.
1675
+ """
1676
+ e = expr
1677
+ while isinstance(e, TpyFieldAccess):
1678
+ e = e.obj
1679
+ return isinstance(e, TpyName)
1680
+
1681
+
1682
+ def stmt_has_any_suspension(stmt: TpyStmt) -> bool:
1683
+ """Walk a statement (its expression slots and sub_bodies) for any
1684
+ suspension point -- a `TpyAwait` (async) or a `TpyYield` (generator).
1685
+ `async with` and `async for` always count even when their bodies have
1686
+ none: the `__aenter__` / `__aexit__` / `__anext__` calls are themselves
1687
+ suspensions. Does not descend into nested function/lambda bodies (a
1688
+ suspension there belongs to the inner callable, not this one)."""
1689
+ if isinstance(stmt, TpyYield):
1690
+ return True
1691
+ if isinstance(stmt, TpyWith) and stmt.is_async:
1692
+ return True
1693
+ if isinstance(stmt, TpyForEach) and stmt.is_async:
1694
+ return True
1695
+
1696
+ def walk_expr(e: TpyExpr | None) -> bool:
1697
+ if e is None:
1698
+ return False
1699
+ if isinstance(e, TpyAwait):
1700
+ return True
1701
+ for c in (e.children() if hasattr(e, "children") else ()):
1702
+ if walk_expr(c):
1703
+ return True
1704
+ return False
1705
+
1706
+ if hasattr(stmt, "exprs"):
1707
+ for e in stmt.exprs():
1708
+ if walk_expr(e):
1709
+ return True
1710
+ if hasattr(stmt, "sub_bodies"):
1711
+ for body in stmt.sub_bodies():
1712
+ for s in body:
1713
+ if stmt_has_any_suspension(s):
1714
+ return True
1715
+ return False
1716
+
1717
+
1718
+ def stmts_have_any_suspension(stmts: list[TpyStmt]) -> bool:
1719
+ return any(stmt_has_any_suspension(s) for s in stmts)
1720
+
1721
+
1722
+ def stmts_have_any_return(stmts: list[TpyStmt]) -> bool:
1723
+ """True if any of `stmts` (recursively through sub_bodies) contains a
1724
+ TpyReturn. Does not descend into nested function/lambda bodies."""
1725
+ for s in stmts:
1726
+ if isinstance(s, TpyReturn):
1727
+ return True
1728
+ if hasattr(s, "sub_bodies"):
1729
+ for b in s.sub_bodies():
1730
+ if stmts_have_any_return(b):
1731
+ return True
1732
+ return False