tpy-lang 0.3.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
  2. tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
  3. tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
  4. tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
  5. tpyc/__init__.py +104 -0
  6. tpyc/__main__.py +6 -0
  7. tpyc/_buildinfo.py +1 -0
  8. tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
  9. tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
  10. tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
  11. tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
  12. tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
  13. tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
  14. tpyc/_data/lib/tpy/_functools_macros.py +80 -0
  15. tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
  16. tpyc/_data/lib/tpy/argparse.py +2062 -0
  17. tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
  18. tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
  19. tpyc/_data/lib/tpy/base64.py +410 -0
  20. tpyc/_data/lib/tpy/bisect.py +39 -0
  21. tpyc/_data/lib/tpy/builtins.py +38 -0
  22. tpyc/_data/lib/tpy/dataclasses.py +354 -0
  23. tpyc/_data/lib/tpy/enum.py +23 -0
  24. tpyc/_data/lib/tpy/functools.py +33 -0
  25. tpyc/_data/lib/tpy/hashlib.py +206 -0
  26. tpyc/_data/lib/tpy/heapq.py +118 -0
  27. tpyc/_data/lib/tpy/io.py +395 -0
  28. tpyc/_data/lib/tpy/json.py +221 -0
  29. tpyc/_data/lib/tpy/math.py +406 -0
  30. tpyc/_data/lib/tpy/random.py +597 -0
  31. tpyc/_data/lib/tpy/re.py +467 -0
  32. tpyc/_data/lib/tpy/socket.py +379 -0
  33. tpyc/_data/lib/tpy/struct.py +178 -0
  34. tpyc/_data/lib/tpy/sys.py +40 -0
  35. tpyc/_data/lib/tpy/time.py +39 -0
  36. tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
  37. tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
  38. tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
  39. tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
  40. tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
  41. tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
  42. tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
  43. tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
  44. tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
  45. tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
  46. tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
  47. tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
  48. tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
  49. tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
  50. tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
  51. tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
  52. tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
  53. tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
  54. tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
  55. tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
  56. tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
  57. tpyc/_data/lib/tpy/tpy/_version.py +29 -0
  58. tpyc/_data/lib/tpy/tpy/bits.py +28 -0
  59. tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
  60. tpyc/_data/lib/tpy/tpy/extern.py +8 -0
  61. tpyc/_data/lib/tpy/tpy/mem.py +49 -0
  62. tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
  63. tpyc/_data/lib/tpy/tpy/version.py +21 -0
  64. tpyc/_data/lib/tpy/typing.py +13 -0
  65. tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
  66. tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
  67. tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
  68. tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
  69. tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
  70. tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
  71. tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
  72. tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
  73. tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
  74. tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
  75. tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
  76. tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
  77. tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
  78. tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
  79. tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
  80. tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
  81. tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
  82. tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
  83. tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
  84. tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
  85. tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
  86. tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
  87. tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
  88. tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
  89. tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
  90. tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
  91. tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
  92. tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
  93. tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
  94. tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
  95. tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
  96. tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
  97. tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
  98. tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
  99. tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
  100. tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
  101. tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
  102. tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
  103. tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
  104. tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
  105. tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
  106. tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
  107. tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
  108. tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
  109. tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
  110. tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
  111. tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
  112. tpyc/_data/runtime/cpp/third_party/README.md +58 -0
  113. tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
  114. tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
  115. tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
  116. tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
  117. tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
  118. tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
  119. tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
  120. tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
  121. tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
  122. tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
  123. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
  124. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
  125. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
  126. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
  127. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
  128. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
  129. tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
  130. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
  131. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
  132. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
  133. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
  134. tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
  135. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
  136. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
  137. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
  138. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
  139. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
  140. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
  141. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
  142. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
  143. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
  144. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
  145. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
  146. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
  147. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
  148. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
  149. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
  150. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
  151. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
  152. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
  153. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
  154. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
  155. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
  156. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
  157. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
  158. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
  159. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
  160. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
  161. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
  162. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
  163. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
  164. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
  165. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
  166. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
  167. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
  168. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
  169. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
  170. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
  171. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
  172. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
  173. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
  174. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
  175. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
  176. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
  177. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
  178. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
  179. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
  180. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
  181. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
  182. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
  183. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
  184. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
  185. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
  186. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
  187. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
  188. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
  189. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
  190. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
  191. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
  192. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
  193. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
  194. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
  195. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
  196. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
  197. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
  198. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
  199. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
  200. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
  201. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
  202. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
  203. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
  204. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
  205. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
  206. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
  207. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
  208. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
  209. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
  210. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
  211. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
  212. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
  213. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
  214. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
  215. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
  216. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
  217. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
  218. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
  219. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
  220. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
  221. tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
  222. tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
  223. tpyc/build/__init__.py +7 -0
  224. tpyc/build/pcre2.py +122 -0
  225. tpyc/build/third_party.py +413 -0
  226. tpyc/cli.py +822 -0
  227. tpyc/codegen_cpp/__init__.py +18 -0
  228. tpyc/codegen_cpp/builtins.py +484 -0
  229. tpyc/codegen_cpp/context.py +2064 -0
  230. tpyc/codegen_cpp/expressions.py +5940 -0
  231. tpyc/codegen_cpp/functions.py +1913 -0
  232. tpyc/codegen_cpp/gen_async.py +3258 -0
  233. tpyc/codegen_cpp/gen_generators.py +657 -0
  234. tpyc/codegen_cpp/generator.py +2258 -0
  235. tpyc/codegen_cpp/match.py +1997 -0
  236. tpyc/codegen_cpp/param_const.py +172 -0
  237. tpyc/codegen_cpp/protocols.py +907 -0
  238. tpyc/codegen_cpp/records.py +1654 -0
  239. tpyc/codegen_cpp/resumable_cfg.py +1651 -0
  240. tpyc/codegen_cpp/statements.py +4963 -0
  241. tpyc/codegen_cpp/string_dispatch.py +76 -0
  242. tpyc/codegen_cpp/test_context.py +46 -0
  243. tpyc/codegen_cpp/test_param_const.py +113 -0
  244. tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
  245. tpyc/codegen_cpp/type_resolution.py +53 -0
  246. tpyc/codegen_cpp/types.py +436 -0
  247. tpyc/codegen_cpp/variant_access.py +135 -0
  248. tpyc/coercions.py +749 -0
  249. tpyc/compilation_context.py +57 -0
  250. tpyc/compiler.py +3945 -0
  251. tpyc/cycle_detection.py +358 -0
  252. tpyc/diagnostics.py +135 -0
  253. tpyc/dump_types.py +353 -0
  254. tpyc/frontend_diagnostics.py +47 -0
  255. tpyc/frontend_ir/__init__.py +140 -0
  256. tpyc/frontend_ir/lower.py +1098 -0
  257. tpyc/frontend_ir/nodes.py +718 -0
  258. tpyc/frontend_ir/resolver_adapter.py +151 -0
  259. tpyc/frontend_plugin.py +209 -0
  260. tpyc/install_docs.py +81 -0
  261. tpyc/liveness.py +756 -0
  262. tpyc/macro_api.py +1724 -0
  263. tpyc/macro_loader.py +497 -0
  264. tpyc/module_names.py +64 -0
  265. tpyc/modules/__init__.py +31 -0
  266. tpyc/modules/defs.py +89 -0
  267. tpyc/modules/registry.py +36 -0
  268. tpyc/modules/resolver.py +192 -0
  269. tpyc/modules/type_resolution.py +629 -0
  270. tpyc/namespace.py +172 -0
  271. tpyc/parse/__init__.py +84 -0
  272. tpyc/parse/imports.py +490 -0
  273. tpyc/parse/nodes.py +1732 -0
  274. tpyc/parse/parser.py +4043 -0
  275. tpyc/parse/resolve_refs.py +466 -0
  276. tpyc/parse/type_resolver.py +1060 -0
  277. tpyc/prescan.py +254 -0
  278. tpyc/qnames.py +149 -0
  279. tpyc/repl.py +529 -0
  280. tpyc/repl_backends.py +848 -0
  281. tpyc/sema/__init__.py +21 -0
  282. tpyc/sema/analyzer.py +3625 -0
  283. tpyc/sema/bound_check.py +72 -0
  284. tpyc/sema/builder_trace.py +684 -0
  285. tpyc/sema/calls.py +5406 -0
  286. tpyc/sema/compatibility.py +2107 -0
  287. tpyc/sema/context.py +1243 -0
  288. tpyc/sema/expressions.py +3737 -0
  289. tpyc/sema/flow_facts.py +199 -0
  290. tpyc/sema/init_tracker.py +150 -0
  291. tpyc/sema/list_literals.py +69 -0
  292. tpyc/sema/literal_utils.py +27 -0
  293. tpyc/sema/local_deduction.py +1088 -0
  294. tpyc/sema/macros.py +179 -0
  295. tpyc/sema/match.py +1177 -0
  296. tpyc/sema/method_expansion.py +347 -0
  297. tpyc/sema/methods.py +2197 -0
  298. tpyc/sema/mutation_propagation.py +268 -0
  299. tpyc/sema/narrowing.py +857 -0
  300. tpyc/sema/numeric_lattice.py +160 -0
  301. tpyc/sema/operators.py +402 -0
  302. tpyc/sema/overloads.py +841 -0
  303. tpyc/sema/protocols.py +1209 -0
  304. tpyc/sema/reach_analysis.py +202 -0
  305. tpyc/sema/registration.py +3156 -0
  306. tpyc/sema/scope_tracker.py +193 -0
  307. tpyc/sema/statements.py +4426 -0
  308. tpyc/sema/type_ops.py +1879 -0
  309. tpyc/sema/value_range.py +181 -0
  310. tpyc/symbol_binding.py +259 -0
  311. tpyc/test_c3_mro.py +208 -0
  312. tpyc/test_cli_argv.py +52 -0
  313. tpyc/test_compiler.py +559 -0
  314. tpyc/test_contains_type_param.py +101 -0
  315. tpyc/test_cycle_detection.py +221 -0
  316. tpyc/test_dump_types.py +225 -0
  317. tpyc/test_install_docs.py +65 -0
  318. tpyc/test_local_cpp_form.py +135 -0
  319. tpyc/test_macro_loader.py +76 -0
  320. tpyc/test_method_expansion.py +254 -0
  321. tpyc/test_nominal_identity.py +182 -0
  322. tpyc/test_overloads.py +410 -0
  323. tpyc/test_parse.py +303 -0
  324. tpyc/test_parse_type_ref.py +506 -0
  325. tpyc/test_parse_version_info.py +58 -0
  326. tpyc/test_reach_analysis.py +72 -0
  327. tpyc/test_ref_type.py +216 -0
  328. tpyc/test_send_sync_substitution.py +276 -0
  329. tpyc/test_tuple_mutation_propagation.py +206 -0
  330. tpyc/test_type_def_registry.py +1729 -0
  331. tpyc/test_union_types.py +195 -0
  332. tpyc/type_def_registry.py +975 -0
  333. tpyc/typesys.py +5104 -0
tpyc/sema/context.py ADDED
@@ -0,0 +1,1243 @@
1
+ """
2
+ TurboPython Semantic Analysis Context
3
+
4
+ Contains the shared state that is passed to all semantic analysis components.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from contextlib import contextmanager
9
+ from copy import deepcopy
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Any, Iterator, Literal, TYPE_CHECKING
13
+
14
+ from ..macro_loader import MacroRegistry
15
+ from ..parse.nodes import SourceLocation
16
+ from .value_range import ValueRange
17
+
18
+ if TYPE_CHECKING:
19
+ from ..parse.type_resolver import TypeResolver
20
+
21
+ from ..typesys import (
22
+ TpyType, TypeRegistry, ListLiteralInfo, DictLiteralInfo, SetLiteralInfo, ViewVarInfo, TypeParamKind, IntLiteralType,
23
+ INT32, BIGINT, NominalType, ReadonlyType, OwnType, OptionalType, UnionType, TupleType,
24
+ RecursiveAliasInstanceType, recursive_union_alternatives,
25
+ PendingListType, PendingDictType, PendingSetType,
26
+ PendingGenericInstanceType, PendingGenericInstanceInfo,
27
+ ViewTypeFamily, PendingViewType, PendingStrType, VIEW_TYPE_FAMILIES,
28
+ unwrap_readonly, unwrap_ref_type, unwrap_qualifiers,
29
+ is_dyn_protocol,
30
+ )
31
+ from ..namespace import Namespace
32
+ from ..type_def_registry import int_traits_of
33
+ from ..parse import (
34
+ TpyExpr, TpyStmt, TpyRecord, TpyFunction, TpyVarDecl, TpyMethodCall,
35
+ TpyCall, TpyCoerce, TpyName, TpySubscript, TpyFieldAccess, TpyBinOp, TpyIfExpr,
36
+ TpyNestedDef,
37
+ )
38
+ from ..diagnostics import Diagnostic, DiagnosticLevel, SemanticError, Scope
39
+
40
+ # Tuple of all pending container types -- use in isinstance checks so adding
41
+ # a new container type requires updating only this one constant.
42
+ PENDING_CONTAINER_TYPES = (PendingListType, PendingDictType, PendingSetType)
43
+
44
+
45
+ def addr_taken_roots(expr: TpyExpr) -> list[str]:
46
+ """Return all variable names whose storage is potentially aliased by expr.
47
+
48
+ Used to mark params as mutated when their address is taken (directly or
49
+ implicitly). Handles or/and (TpyBinOp ||/&&) and ternary (TpyIfExpr),
50
+ returning all possible roots across branches.
51
+ """
52
+ if isinstance(expr, TpyCoerce):
53
+ return addr_taken_roots(expr.expr)
54
+ if isinstance(expr, TpyName):
55
+ return [expr.name]
56
+ if isinstance(expr, TpySubscript):
57
+ return addr_taken_roots(expr.obj)
58
+ if isinstance(expr, TpyFieldAccess):
59
+ return addr_taken_roots(expr.obj)
60
+ if isinstance(expr, TpyBinOp) and expr.op in ("||", "&&"):
61
+ return addr_taken_roots(expr.left) + addr_taken_roots(expr.right)
62
+ if isinstance(expr, TpyIfExpr):
63
+ return addr_taken_roots(expr.then_expr) + addr_taken_roots(expr.else_expr)
64
+ return []
65
+
66
+
67
+ def _storage_key(expr: TpyExpr) -> str | None:
68
+ """Extract storage key: 'name' or 'name.field' for single-level field access.
69
+
70
+ Used by the borrow tracker as a dict key to identify storage that can be
71
+ borrowed from or mutated. Supports dotted paths so that ``self.items``
72
+ and ``obj.field`` are tracked separately from ``self`` / ``obj``.
73
+ """
74
+ if isinstance(expr, TpyName):
75
+ return expr.name
76
+ if isinstance(expr, TpyFieldAccess) and isinstance(expr.obj, TpyName):
77
+ return f"{expr.obj.name}.{expr.field}"
78
+ return None
79
+
80
+
81
+ def _storage_root(key: str) -> str:
82
+ """Strip the field suffix off a dotted storage key ('self.items' -> 'self').
83
+
84
+ Inverse of `_storage_key`: callers that need to look up the variable name
85
+ a dotted key was built from (param-name table, loop-var dict) use this.
86
+ """
87
+ dot = key.find(".")
88
+ return key if dot == -1 else key[:dot]
89
+
90
+
91
+ def expr_yields_non_null_ptr(expr: TpyExpr, non_null_vars: set[str]) -> bool:
92
+ """Whether evaluating `expr` produces a Ptr value with known non-null provenance.
93
+
94
+ Three independent sources, all unified here so consumers (local-init,
95
+ rebind, future return-site / expression-context uses) don't re-enumerate:
96
+
97
+ 1. Address-taking coercion (`&{e}` -- the `produces_non_null_ptr` flag on
98
+ the Coercion). Covers `p: Ptr[T] = x` and the inheritance / @dynamic
99
+ upcasts. A chain of coercions is walked: any non-address-taking outer
100
+ coercion (e.g. the trivial `Ptr[T] -> Ptr[readonly[T]]`) is transparent
101
+ and provenance is inherited from the inner expression.
102
+ 2. Ptr-returning call. Either the call's return type is itself Ptr-typed
103
+ (`take_ptr(x)`, explicit `Ptr(x)` constructor) or the function carries
104
+ the `@value_ptr_coercion` flag (its Ptr param coerces a T arg via `&`).
105
+ 3. Read from a name that earlier analysis already marked non-null.
106
+ """
107
+ while isinstance(expr, TpyCoerce):
108
+ if expr.coercion.produces_non_null_ptr:
109
+ return True
110
+ expr = expr.expr
111
+ if isinstance(expr, TpyCall) and expr.args:
112
+ if expr.call_type is not None and expr.call_type.is_pointer():
113
+ return True
114
+ fi = expr.resolved_function_info
115
+ if fi is not None and fi.value_ptr_coercion:
116
+ return True
117
+ if isinstance(expr, TpyName):
118
+ return expr.name in non_null_vars
119
+ return False
120
+
121
+
122
+ def _borrow_storage_root(expr: TpyExpr) -> str | None:
123
+ """Extract the storage key whose storage is borrowed by this expression.
124
+
125
+ Handles simple names (``items``), subscript on names or single-level
126
+ field access (``items[i]``, ``self.items[i]``), and field access on
127
+ names (``obj.field``). Returns None for deeper nesting or rvalues
128
+ (conservative -- no borrow registered).
129
+ """
130
+ if isinstance(expr, TpyCoerce):
131
+ return _borrow_storage_root(expr.expr)
132
+ if isinstance(expr, TpyName):
133
+ return expr.name
134
+ if isinstance(expr, TpySubscript):
135
+ return _storage_key(expr.obj)
136
+ if isinstance(expr, TpyFieldAccess):
137
+ return _storage_key(expr)
138
+ return None
139
+
140
+
141
+ class BorrowKind(Enum):
142
+ """Kind of borrow relationship between a borrower and its storage."""
143
+ ALIAS = "alias" # whole-container alias (safe through mutations)
144
+ FIELD = "field" # field-level reference
145
+ ITER = "iter" # for-loop iterator
146
+ ELEMENT = "element" # subscript element reference
147
+ PTR = "ptr" # pointer into storage
148
+
149
+
150
+ # Restrictiveness order: PTR/ITER/ELEMENT > FIELD > ALIAS. Used by retarget
151
+ # logic to promote a chained borrow to the most-restrictive kind in the chain
152
+ # (an ALIAS of an ELEMENT borrower is invalidated by structural mutation of
153
+ # the source container) and by FlowFacts borrow merging.
154
+ BORROW_KIND_RANK: dict[BorrowKind, int] = {
155
+ BorrowKind.ALIAS: 0,
156
+ BorrowKind.FIELD: 1,
157
+ BorrowKind.ITER: 2,
158
+ BorrowKind.ELEMENT: 3,
159
+ BorrowKind.PTR: 3,
160
+ }
161
+
162
+
163
+ class BorrowTracker:
164
+ """Tracks borrow relationships between variables for mutation safety.
165
+
166
+ Manages which variables borrow storage from other variables, enabling
167
+ conflict detection when borrowed storage is mutated.
168
+
169
+ Note: string view source tracking (str_source_borrows) and loop var
170
+ mutation tracking (mutated_loop_vars) remain on SemanticContext since
171
+ they reference string deduction and loop optimization state respectively.
172
+ TODO: consider unifying all borrow-like tracking here if a broader
173
+ "deferred type tracking" system emerges.
174
+ """
175
+
176
+ __slots__ = ('borrows', 'borrow_kinds')
177
+
178
+ def __init__(self) -> None:
179
+ self.borrows: dict[str, set[str]] = {}
180
+ self.borrow_kinds: dict[tuple[str, str], BorrowKind] = {}
181
+
182
+ def reset(self) -> None:
183
+ """Clear all borrow state (called between function analyses)."""
184
+ self.borrows.clear()
185
+ self.borrow_kinds.clear()
186
+
187
+ def add_borrow(self, storage: str, borrower: str, kind: BorrowKind = BorrowKind.ALIAS) -> None:
188
+ """Record that ``borrower`` borrows from ``storage``."""
189
+ self.borrows.setdefault(storage, set()).add(borrower)
190
+ self.borrow_kinds[(storage, borrower)] = kind
191
+
192
+ def remove_borrower(self, borrower: str) -> None:
193
+ """Remove all borrows held by ``borrower`` (e.g. on reassignment)."""
194
+ to_clean: list[str] = []
195
+ for storage, borrowers in self.borrows.items():
196
+ if borrower in borrowers:
197
+ borrowers.discard(borrower)
198
+ self.borrow_kinds.pop((storage, borrower), None)
199
+ if not borrowers:
200
+ to_clean.append(storage)
201
+ for storage in to_clean:
202
+ del self.borrows[storage]
203
+
204
+ def retarget_storage_borrows(self, storage: str) -> None:
205
+ """Reassignment of ``storage``: retarget its borrowers to the upstream source.
206
+
207
+ The reassigned variable is generated as a ``T*`` pointer-local in C++,
208
+ so any borrower like ``view = storage`` aliases whatever ``storage``
209
+ currently points into. After the reassignment we want chains to keep
210
+ tracking the original container, not break silently.
211
+
212
+ The retargeted borrow kind is the most-restrictive in the chain
213
+ (PTR/ITER/ELEMENT > FIELD > ALIAS): an ALIAS of an ELEMENT borrower
214
+ is still invalidated by structural mutation of the source container.
215
+
216
+ Field-path borrows (``storage.*``) refer to the old object's fields
217
+ and are dropped unconditionally -- the variable will be rebound to a
218
+ different object, so those paths are unreachable.
219
+
220
+ If ``storage`` itself was not borrowing anything (no upstream chain),
221
+ its borrowers had nowhere to retarget to and are dropped.
222
+ """
223
+ upstream = self.borrow_source(storage)
224
+ upstream_kind = self.borrow_kinds.get((upstream, storage)) if upstream else None
225
+
226
+ borrowers = self.borrows.pop(storage, None)
227
+ if borrowers:
228
+ if upstream is not None and upstream_kind is not None:
229
+ for b in borrowers:
230
+ child_kind = self.borrow_kinds.pop((storage, b), None)
231
+ if child_kind is None:
232
+ continue
233
+ promoted = (child_kind
234
+ if BORROW_KIND_RANK[child_kind] >= BORROW_KIND_RANK[upstream_kind]
235
+ else upstream_kind)
236
+ existing = self.borrow_kinds.get((upstream, b))
237
+ if existing is None or BORROW_KIND_RANK[promoted] > BORROW_KIND_RANK[existing]:
238
+ self.borrows.setdefault(upstream, set()).add(b)
239
+ self.borrow_kinds[(upstream, b)] = promoted
240
+ else:
241
+ for b in borrowers:
242
+ self.borrow_kinds.pop((storage, b), None)
243
+
244
+ # Field-path borrows (storage.X) are dropped: the variable rebinds to
245
+ # a different object, so dotted-key borrows are unreachable.
246
+ prefix = storage + "."
247
+ to_remove = [k for k in self.borrows if k.startswith(prefix)]
248
+ for k in to_remove:
249
+ for b in self.borrows.pop(k):
250
+ self.borrow_kinds.pop((k, b), None)
251
+
252
+ def has_iter_borrow(self, storage_name: str) -> bool:
253
+ """Check if a variable is borrowed by an active for-loop iterator."""
254
+ borrowers = self.borrows.get(storage_name)
255
+ return borrowers is not None and "__for_iter" in borrowers
256
+
257
+ def has_element_borrow(self, storage_name: str) -> bool:
258
+ """Check if a variable has element-level or iterator borrows.
259
+
260
+ Returns True for borrows that can be invalidated by structural
261
+ mutations (reallocation, insertion, deletion). Returns False for
262
+ whole-container alias borrows which are safe through mutations.
263
+ """
264
+ borrowers = self.borrows.get(storage_name)
265
+ if not borrowers:
266
+ return False
267
+ _INVALIDATING = (BorrowKind.ITER, BorrowKind.ELEMENT, BorrowKind.PTR)
268
+ return any(
269
+ self.borrow_kinds.get((storage_name, b)) in _INVALIDATING
270
+ for b in borrowers
271
+ )
272
+
273
+ def has_borrow_of_kinds(self, storage: str, kinds: tuple[BorrowKind, ...]) -> bool:
274
+ """Check if any borrower of storage has one of the given borrow kinds."""
275
+ borrowers = self.borrows.get(storage)
276
+ if not borrowers:
277
+ return False
278
+ return any(
279
+ self.borrow_kinds.get((storage, b)) in kinds
280
+ for b in borrowers
281
+ )
282
+
283
+ def effective_storage(self, name: str) -> str:
284
+ """Resolve alias chains to find the underlying storage.
285
+
286
+ If ``name`` is an alias of another variable, follows the chain
287
+ (e.g. b -> a -> items) and returns the root storage. Returns
288
+ ``name`` itself when it is not an alias borrower.
289
+ """
290
+ visited: set[str] = {name}
291
+ current = name
292
+ while True:
293
+ found = None
294
+ for storage, borrowers in self.borrows.items():
295
+ if current in borrowers and self.borrow_kinds.get((storage, current)) is BorrowKind.ALIAS:
296
+ found = storage
297
+ break
298
+ if found is None or found in visited:
299
+ return current
300
+ visited.add(found)
301
+ current = found
302
+
303
+ def borrow_kind_of(self, name: str) -> 'BorrowKind | None':
304
+ """Return the kind of borrow that 'name' holds, or None if not a borrower."""
305
+ for storage, borrowers in self.borrows.items():
306
+ if name in borrowers:
307
+ return self.borrow_kinds.get((storage, name))
308
+ return None
309
+
310
+ def is_deferred_borrow(self, name: str) -> bool:
311
+ """Return True if name ultimately traces back to a deferred ELEMENT borrow.
312
+
313
+ A variable is deferred if it is an ELEMENT borrow, or an ALIAS/FIELD borrow
314
+ of a deferred variable (transitively). PTR/ITER borrows are not deferred.
315
+ Termination is guaranteed because borrow chains are acyclic.
316
+ """
317
+ kind = self.borrow_kind_of(name)
318
+ if kind == BorrowKind.ELEMENT:
319
+ return True
320
+ if kind in (BorrowKind.ALIAS, BorrowKind.FIELD):
321
+ source = self.borrow_source(name)
322
+ if source is not None:
323
+ return self.is_deferred_borrow(source)
324
+ return False
325
+
326
+ def borrow_source(self, name: str) -> str | None:
327
+ """Return the direct borrow source of name (any borrow kind), or None.
328
+
329
+ Unlike effective_storage (ALIAS-only), this finds the container for
330
+ ITER/ELEMENT/FIELD/PTR borrows too -- used to trace loop-var addresses
331
+ back to the source container.
332
+ """
333
+ for storage, borrowers in self.borrows.items():
334
+ if name in borrowers:
335
+ return storage
336
+ return None
337
+
338
+ def effective_storage_through_borrows(self, name: str) -> str:
339
+ """Follow ALL borrow chains (ALIAS + ELEMENT + FIELD + PTR) to ultimate storage.
340
+
341
+ Unlike effective_storage (ALIAS-only), this traverses the full chain
342
+ so a write through an element ref can be traced back to its source param.
343
+ For example: w ALIAS-borrows v, v ELEMENT-borrows items -> returns items.
344
+ Cycle-safe via visited set.
345
+ """
346
+ visited: set[str] = {name}
347
+ current = name
348
+ while True:
349
+ found = None
350
+ for storage, borrowers in self.borrows.items():
351
+ if current in borrowers:
352
+ found = storage
353
+ break
354
+ if found is None or found in visited:
355
+ return current
356
+ visited.add(found)
357
+ current = found
358
+
359
+ def freeze(self) -> frozenset[tuple[str, str, BorrowKind]]:
360
+ """Snapshot borrow state as immutable triples for flow analysis."""
361
+ return frozenset(
362
+ (storage, borrower, self.borrow_kinds[(storage, borrower)])
363
+ for storage, borrowers in self.borrows.items()
364
+ for borrower in borrowers
365
+ )
366
+
367
+ def restore_from_frozen(self, triples: frozenset[tuple[str, str, BorrowKind]]) -> None:
368
+ """Restore borrow state from frozen triples."""
369
+ self.borrows.clear()
370
+ self.borrow_kinds.clear()
371
+ for storage, borrower, kind in triples:
372
+ self.borrows.setdefault(storage, set()).add(borrower)
373
+ self.borrow_kinds[(storage, borrower)] = kind
374
+
375
+
376
+ class _ModuleInitSentinel:
377
+ """Sentinel for module-level init context (not a real function, but not None either).
378
+
379
+ Truthy so that `if ctx.func.current_function:` passes, but fails
380
+ `isinstance(ctx.func.current_function, TpyFunction)` checks.
381
+ """
382
+ __slots__ = ()
383
+ def __bool__(self) -> bool:
384
+ return True
385
+ def __repr__(self) -> str:
386
+ return "<MODULE_INIT>"
387
+
388
+
389
+ MODULE_INIT_CONTEXT = _ModuleInitSentinel()
390
+
391
+
392
+ def is_body_like_scope(current_function: 'TpyFunction | _ModuleInitSentinel | None') -> bool:
393
+ """True when the current context supports pending-type resolution.
394
+
395
+ Real function bodies and ``MODULE_INIT_CONTEXT`` both have a
396
+ ``FunctionTrackingState`` with ``pending_resolutions`` plumbed through
397
+ and ``deduction.resolve_all()`` runs at the end of both (see
398
+ ``analyzer._analyze_top_level``). Class bodies, registration-time
399
+ contexts, and no-context states do not, so empty literals and similar
400
+ late-typed constructs must reject there.
401
+ """
402
+ return isinstance(current_function, TpyFunction) or current_function is MODULE_INIT_CONTEXT
403
+
404
+
405
+ @dataclass
406
+ class RecordContext:
407
+ """State for the record currently being analyzed (type params, bounds)."""
408
+ record: TpyRecord | None = None
409
+ type_params: list[str] | None = None
410
+ type_param_kinds: list[TypeParamKind] | None = None
411
+ type_param_bounds: dict[str, TpyType] | None = None
412
+
413
+
414
+ @dataclass
415
+ class FunctionTrackingState:
416
+ """Per-function analysis state.
417
+
418
+ Extracted from SemanticContext to keep that class from growing unbounded.
419
+ Reset between functions and saved/restored for nested-def isolation.
420
+ Accessed explicitly by callers via ``ctx.func.<field>``.
421
+ """
422
+
423
+ # --- Analysis state (per-function) ---
424
+ current_scope: Scope | None = None
425
+ current_function: TpyFunction | _ModuleInitSentinel | None = None
426
+ current_ns: Namespace | None = None
427
+ loop_depth: int = 0
428
+
429
+ # --- List/dict/set literal tracking ---
430
+ variable_to_literal: dict[str, int] = field(default_factory=dict)
431
+ pending_resolutions: list[int] = field(default_factory=list)
432
+ pre_analyzed_method_args: dict[int, list[TpyType]] = field(default_factory=dict)
433
+ variable_to_dict_literal: dict[str, int] = field(default_factory=dict)
434
+ pending_dict_resolutions: list[int] = field(default_factory=list)
435
+ variable_to_set_literal: dict[str, int] = field(default_factory=dict)
436
+ pending_set_resolutions: list[int] = field(default_factory=list)
437
+
438
+ # --- Pending generic instance tracking ---
439
+ pending_generic_instances: dict[int, PendingGenericInstanceInfo] = field(default_factory=dict)
440
+ variable_to_generic_instance: dict[str, int] = field(default_factory=dict)
441
+
442
+ # --- String local tracking ---
443
+ variable_to_str_var: dict[str, int] = field(default_factory=dict)
444
+ str_source_borrows: dict[str, set[int]] = field(default_factory=dict)
445
+ pending_str_resolutions: list[int] = field(default_factory=list)
446
+
447
+ # --- Bytes local tracking ---
448
+ variable_to_bytes_var: dict[str, int] = field(default_factory=dict)
449
+ bytes_source_borrows: dict[str, set[int]] = field(default_factory=dict)
450
+ pending_bytes_resolutions: list[int] = field(default_factory=list)
451
+
452
+ # --- Pinned-view alias tracking (StrView/BytesView annotations) ---
453
+ # source_name -> set of pinned-view borrower names. Pending views fall
454
+ # back to the owned type via source_mutated; pinned views can't, so we
455
+ # warn at source reassignment that the view dangles.
456
+ pinned_view_aliases: dict[str, set[str]] = field(default_factory=dict)
457
+
458
+ # --- Control flow ---
459
+ super_init_call: TpyMethodCall | None = None
460
+ super_del_call: TpyMethodCall | None = None
461
+ pending_loop_vars: dict[str, tuple[TpyType, TpyStmt, TpyStmt | None]] = field(default_factory=dict)
462
+ loop_vars: set[str] = field(default_factory=set)
463
+ mutated_loop_vars: set[str] = field(default_factory=set)
464
+ consumed_loop_vars: set[str] = field(default_factory=set)
465
+ deferred_loop_copy_warnings: dict[str, list[int]] = field(default_factory=dict)
466
+ loop_var_iterable: dict[str, str] = field(default_factory=dict)
467
+
468
+ # --- Scope escape tracking ---
469
+ var_scope_depth: dict[str, int] = field(default_factory=dict)
470
+ hoisted_vars: set[str] = field(default_factory=set)
471
+ rvalue_vars: set[str] = field(default_factory=set)
472
+ owned_locals: set[str] = field(default_factory=set)
473
+ # Accumulator: all locals that were ever owned. Survives FlowFacts
474
+ # save/restore (not in FlowFacts). Used to compute the exported
475
+ # movable_locals set at function end.
476
+ ever_owned_locals: set[str] = field(default_factory=set)
477
+ move_through_vars: set[str] = field(default_factory=set)
478
+
479
+ # --- Prescan / last-use ---
480
+ current_reassigned_vars: set[str] = field(default_factory=set)
481
+ current_lvalue_reassigned: set[str] = field(default_factory=set)
482
+ current_aug_assigned_vars: set[str] = field(default_factory=set)
483
+
484
+ # --- Definite-assignment tracking ---
485
+ definitely_assigned: set[str] = field(default_factory=set)
486
+ init_terminated: bool = False
487
+ narrowed_types: dict[str, TpyType] = field(default_factory=dict)
488
+
489
+ # --- Reassignment inference tracking ---
490
+ literal_default_vars: set[str] = field(default_factory=set)
491
+ literal_values: dict[str, list[int]] = field(default_factory=dict)
492
+ unresolved_none_vars: set[str] = field(default_factory=set)
493
+ write_history: dict[str, list[tuple[TpyType, TpyExpr]]] = field(default_factory=dict)
494
+ authoritative_types: dict[str, TpyType] = field(default_factory=dict)
495
+ authoritative_type_lines: dict[str, int] = field(default_factory=dict)
496
+ # Locals whose type was retroactively promoted from the literal-seeded
497
+ # default to a fixed-int target by a typed-slot use (ARG, RETURN, INIT,
498
+ # ASSIGN, SETITEM, FIELD, dict-key). Maps name to the loc of the use
499
+ # that triggered the promotion -- surfaced in later type-mismatch
500
+ # errors when a subsequent use disagrees with the locked type, and
501
+ # consulted by _analyze_assign to refresh stale existing_type when
502
+ # the RHS triggered the promotion of the LHS.
503
+ retro_widened_locs: dict[str, 'SourceLocation | None'] = field(default_factory=dict)
504
+
505
+ # --- Global declaration tracking ---
506
+ global_declarations: set[str] = field(default_factory=set)
507
+
508
+ # --- Nested def tracking ---
509
+ in_nested_def: bool = False
510
+ nested_def_name: str | None = None
511
+ outer_scope_locals: set[str] = field(default_factory=set)
512
+ current_nonlocal_names: set[str] = field(default_factory=set)
513
+ nested_def_names: set[str] = field(default_factory=set)
514
+ nested_def_escapes: set[str] = field(default_factory=set)
515
+ nested_def_nodes: dict[str, 'TpyNestedDef'] = field(default_factory=dict)
516
+
517
+ # --- Pointer provenance tracking ---
518
+ # Invariant: param_provenance_vars is a subset of safe_to_return_vars.
519
+ # Both sets merge with INTERSECT at branch/loop joins. The subset
520
+ # invariant is what lets a local stay safe-to-return when different
521
+ # branches reach the join via different safe sources (e.g. one branch
522
+ # param-derived, another a trusted call return) -- the OR-of-sources
523
+ # is materialized in safe_to_return_vars at write time, so intersection
524
+ # preserves it even though intersecting param_provenance_vars alone
525
+ # would lose it.
526
+ param_provenance_vars: set[str] = field(default_factory=set)
527
+ safe_to_return_vars: set[str] = field(default_factory=set)
528
+ non_null_ptr_vars: set[str] = field(default_factory=set)
529
+ # Narrowing accumulated during a single statement's expression analysis;
530
+ # flushed into non_null_ptr_vars at the statement boundary. Deferred so
531
+ # sibling accesses within an unspecified-order expression (e.g. `p.x + p.y`,
532
+ # `p.x = p.x + 1`) keep their individual checks -- C++ leaves arithmetic
533
+ # and RHS-vs-LHS sub-expression order unspecified (C++17 only sequences
534
+ # RHS-before-LHS for the whole assignment, not for sub-expressions),
535
+ # so within-statement elision would be unsafe.
536
+ pending_non_null_ptr_vars: set[str] = field(default_factory=set)
537
+
538
+ # --- Consumed variable tracking ---
539
+ consumed_vars: set[str] = field(default_factory=set)
540
+
541
+
542
+ # --- Variable declaration tracking (per-function) ---
543
+ var_decl_by_name: dict[str, 'TpyVarDecl'] = field(default_factory=dict)
544
+
545
+ # --- Integer value range tracking ---
546
+ value_ranges: dict[str, 'ValueRange'] = field(default_factory=dict)
547
+
548
+ # --- Borrow tracking ---
549
+ borrow_tracker: BorrowTracker = field(default_factory=BorrowTracker)
550
+
551
+ # --- Parameter mutation inference ---
552
+ current_param_names: set[str] = field(default_factory=set)
553
+ current_param_name_to_idx: dict[str, int] = field(default_factory=dict)
554
+ current_mutated_param_names: set[str] = field(default_factory=set)
555
+ # Method type params whose `U: T` bound was used representationally in the
556
+ # body (e.g. `Ptr[U] -> Ptr[T]` coercion). Drained to FunctionInfo at body
557
+ # end; codegen reads it at call sites to decide adapter-wrap for structural
558
+ # conformers.
559
+ current_representational_params: set[str] = field(default_factory=set)
560
+ current_rebound_params: set[str] = field(default_factory=set)
561
+ current_call_edges: list = field(default_factory=list)
562
+ current_self_mutated: bool = False
563
+ current_self_struct_mutated: bool = False
564
+ current_struct_mutated_param_names: set[str] = field(default_factory=set)
565
+ current_returned_param_names: set[str] = field(default_factory=set)
566
+ current_consumed_own_params: set[str] = field(default_factory=set)
567
+ # Params whose address has been observed escaping into a mutable Ptr[T]
568
+ # field via `FIELD = PARAM`. Finalized to FunctionInfo.addr_escapes_params
569
+ # at body-analysis end; consumed by param-signature codegen to suppress
570
+ # the `const T&` default so the `&param -> T*` store type-checks.
571
+ current_addr_escape_param_names: set[str] = field(default_factory=set)
572
+
573
+
574
+ @dataclass
575
+ class SemanticContext:
576
+ """Shared state for all semantic analysis components.
577
+
578
+ Per-function tracking state is held in ``func`` (FunctionTrackingState).
579
+ Callers access it explicitly as ``ctx.func.<field>`` so the split between
580
+ module-wide state and per-function state is visible at every use site.
581
+ """
582
+
583
+ # --- Core ---
584
+ registry: TypeRegistry
585
+ global_scope: Scope
586
+ default_int_type: TpyType = field(default_factory=lambda: INT32)
587
+ macro_registry: MacroRegistry | None = None
588
+
589
+ # --- Per-function state ---
590
+ # Accessed explicitly by callers as ``ctx.func.<field>``.
591
+ func: FunctionTrackingState = field(default_factory=FunctionTrackingState)
592
+
593
+ # --- Record context ---
594
+ record_ctx: RecordContext = field(default_factory=RecordContext)
595
+
596
+ # --- Type cache ---
597
+ expr_types: dict[int, TpyType] = field(default_factory=dict)
598
+ var_types: dict[int, TpyType] = field(default_factory=dict)
599
+
600
+ # --- Literal tracking (counters + registries persist across functions) ---
601
+ literal_counter: int = 0
602
+ list_literals: dict[int, ListLiteralInfo] = field(default_factory=dict)
603
+ dict_literals: dict[int, DictLiteralInfo] = field(default_factory=dict)
604
+ set_literals: dict[int, SetLiteralInfo] = field(default_factory=dict)
605
+ pending_generic_counter: int = 0
606
+ str_var_counter: int = 0
607
+ str_vars: dict[int, ViewVarInfo] = field(default_factory=dict)
608
+ bytes_var_counter: int = 0
609
+ bytes_vars: dict[int, ViewVarInfo] = field(default_factory=dict)
610
+
611
+ # --- Test annotation facts (persist across functions) ---
612
+ declared_var_types: dict[tuple[int, str], TpyType] = field(default_factory=dict)
613
+ ptr_deref_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
614
+ subscript_bounds_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
615
+ div_zero_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
616
+ cast_safe_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
617
+
618
+ # --- Import tracking ---
619
+ imports: dict[str, set[tuple[str, str]] | None | str] = field(default_factory=dict)
620
+ bare_module_imports: set[str] = field(default_factory=set)
621
+ imported_names: dict[str, tuple[str, str]] = field(default_factory=dict)
622
+
623
+ # --- Cross-module support ---
624
+ module_name: str = "__main__"
625
+ # File-derived module name used by codegen for namespaces. Identical to
626
+ # `module_name` for non-entry modules; for the entry point, codegen uses
627
+ # the file name while `module_name` stays "__main__" for runtime
628
+ # `__name__` semantics. Set alongside `module_name` in `bind_imports`;
629
+ # consumed by `_register_union_wrappers` so the wrapper-index origin
630
+ # matches the comparison codegen does against its own `module_name`.
631
+ cpp_module_name: str = "__main__"
632
+ module_cpp_namespace: str | None = None # from # tpy: cpp_namespace directive
633
+ # Reference to the current module's `CompiledModule.exports`. Set by
634
+ # `Compiler._finalize_declarations` so the registration paths can
635
+ # find pre-populated skeleton RecordInfo / FunctionInfo /
636
+ # ProtocolInfo / enum NominalType objects (created by
637
+ # `_pre_populate_decl_exports`) and mutate them in place rather than
638
+ # allocating new ones. Peer modules' analyzer registries, populated
639
+ # by `bind_imports` via `module_info.{records,functions,...}`,
640
+ # capture references to the same skeletons; in-place mutation lets
641
+ # those peer registries see the freshly-finalized data without a
642
+ # post-hoc resync. None when no pre-populated exports are
643
+ # attached (e.g. ad-hoc analyzer construction in tests / REPL).
644
+ module_decl_exports: 'object | None' = None
645
+ # Reference to the current module's per-module attribute table
646
+ # (aliased from `CompiledModule.module_attributes`). Registration
647
+ # paths install bindings here as they mint/adopt records,
648
+ # functions, protocols, enums, variables, type aliases, and
649
+ # imports. None when no compiler-provided table is attached
650
+ # (ad-hoc analyzer construction in tests / REPL), in which case
651
+ # `install_binding` no-ops.
652
+ module_attributes: 'dict | None' = None
653
+ top_level_decls: dict[str, int] = field(default_factory=dict)
654
+ # Defining modules this module's code references. Populated by
655
+ # sema.reach_analysis after analysis completes; consumed by codegen
656
+ # to drive transitive include emission.
657
+ reached: set[str] = field(default_factory=set)
658
+
659
+ # --- Final globals ---
660
+ final_globals: set[str] = field(default_factory=set)
661
+ analyzed_finals: set[str] = field(default_factory=set)
662
+
663
+ # --- Builtins ---
664
+ builtin_names: dict[str, TpyType] = field(default_factory=dict)
665
+
666
+ # --- Namespace hierarchy (global/builtins persist) ---
667
+ builtins_ns: Namespace | None = None
668
+ macro_ns: Namespace | None = None
669
+ global_ns: Namespace | None = None
670
+
671
+ # --- Macro dep modules (populated after macros run) ---
672
+ macro_dep_modules: set[str] = field(default_factory=set)
673
+
674
+ # --- Recursive union aliases (self- or mutually-referencing) ---
675
+ recursive_union_names: set[str] = field(default_factory=set)
676
+
677
+ # --- Module resolver (parser-owned) ---
678
+ # The `TypeResolver` the parser built for this module. Wired by
679
+ # `sema.analyzer.analyze()` from `module.resolver`. Consumed by:
680
+ # - `_infer_field_type_from_default` to look up same-module
681
+ # records via the parser's registry (not yet in `ctx.registry`
682
+ # when the field-default pass runs).
683
+ # - `register_record`'s macro post-resolve step (resolves
684
+ # TypeRefNodes in bodies of methods added by class macros,
685
+ # which join the record after the module-level `resolve_refs`
686
+ # walk has already finished).
687
+ # No other sema code should touch this; the main parser ->
688
+ # TpyType binding happens via `parse.resolve_refs.resolve_refs`.
689
+ parser_resolver: 'TypeResolver | None' = None
690
+
691
+ # --- Control flow (persistent) ---
692
+ in_comprehension: int = 0
693
+ sc_and_walrus: set[str] = field(default_factory=set)
694
+ sc_or_walrus: set[str] = field(default_factory=set)
695
+ try_except_error_type: str | None = None
696
+ in_except_tier: Literal["return", "throw"] | None = None
697
+ # Whether the current except handler has an 'as e' binding (needed for return-tier re-raise)
698
+ in_except_has_binding: bool = False
699
+ # True when analyzing a finally body (raise is not allowed there)
700
+ in_finally: bool = False
701
+ is_top_level: bool = False
702
+ # REPL mode: allow @error_return calls at top level (unwrap with panic)
703
+ allow_top_level_error_unwrap: bool = False
704
+ # Re-entry guard for `is_type_nocopy` on a `RecursiveAliasInstanceType`:
705
+ # the wrapper's own self-reference (`list[Tree[T]]` member) would otherwise
706
+ # recurse into Tree[T] forever. Conservative False at the recursion point
707
+ # mirrors `RecursiveAliasInstanceType.is_value_type`'s guard in typesys.
708
+ _evaluating_alias_nocopy: set = field(default_factory=set)
709
+
710
+ # --- Last-use tracking (shared with codegen, persists across functions) ---
711
+ all_last_uses: set[int] = field(default_factory=set)
712
+
713
+ # --- Consuming method tracking ---
714
+ in_consuming_method: bool = False
715
+
716
+ # --- Expression type hint ---
717
+ expr_type_hint: TpyType | None = None
718
+
719
+ # --- Branch-declared variable tracking ---
720
+ if_branch_decls: dict[int, dict[str, TpyType]] = field(default_factory=dict)
721
+
722
+ # --- Extern symbol tracking ---
723
+ extern_symbols: dict[str, str] = field(default_factory=dict)
724
+
725
+ # --- Builder-trace fresh-name counters ---
726
+ # hint -> next index. Shared across BuilderTraceExpander instances
727
+ # so two functions that each open an ArgumentParser trace don't
728
+ # both mint __tpy_builder_argparse_args_1, which would collide as
729
+ # a top-level record/function name in the synthesized module.
730
+ builder_trace_fresh_counters: dict[str, int] = field(default_factory=dict)
731
+
732
+ # --- Diagnostics ---
733
+ diagnostics: list[Diagnostic] = field(default_factory=list)
734
+
735
+ def is_readonly_name(self, name: str) -> bool:
736
+ """Check if a variable has readonly provenance in its declared scope type.
737
+
738
+ isinstance narrowing updates narrowed_types (not scope bindings), so the
739
+ scope binding preserves ReadonlyType even when the expr type is narrowed
740
+ to a concrete member. Reassignment updates the scope binding, so a
741
+ non-readonly reassignment correctly clears this.
742
+ """
743
+ if self.func.current_scope is None:
744
+ return False
745
+ declared = self.func.current_scope.lookup(name)
746
+ return declared is not None and isinstance(declared, ReadonlyType)
747
+
748
+ def _resolve_loc(self, node: TpyExpr | TpyStmt | TpyRecord | None) -> SourceLocation | None:
749
+ """Resolve source location from a node, with fallback to current function/record."""
750
+ loc = getattr(node, 'loc', None) if node else None
751
+ if loc is None and isinstance(self.func.current_function, TpyFunction):
752
+ loc = self.func.current_function.loc
753
+ if loc is None and self.record_ctx.record is not None:
754
+ loc = self.record_ctx.record.loc
755
+ return loc
756
+
757
+ def error(self, message: str, node: TpyExpr | TpyStmt | None = None) -> SemanticError:
758
+ """Create a SemanticError with location from a node."""
759
+ return SemanticError(message, self._resolve_loc(node))
760
+
761
+ def emit_error(self, message: str, node: TpyExpr | TpyStmt | None = None) -> None:
762
+ """Record an error diagnostic without raising (allows continued analysis)."""
763
+ self.diagnostics.append(Diagnostic(DiagnosticLevel.ERROR, message, self._resolve_loc(node)))
764
+
765
+ def warning(self, message: str, node: TpyExpr | TpyStmt | TpyRecord | None = None) -> None:
766
+ """Record a warning diagnostic (doesn't stop compilation)."""
767
+ self.diagnostics.append(Diagnostic(DiagnosticLevel.WARNING, message, self._resolve_loc(node)))
768
+
769
+ def warning_from_loc(self, message: str, loc: 'SourceLocation | None') -> None:
770
+ """Record a warning diagnostic from a SourceLocation."""
771
+ self.diagnostics.append(Diagnostic(DiagnosticLevel.WARNING, message, loc))
772
+
773
+ def nocopy_reason(self, typ: TpyType) -> str:
774
+ """Return a human-readable reason why a type is nocopy.
775
+
776
+ For explicitly @nocopy types returns "@nocopy type 'X'".
777
+ For implicitly nocopy types (propagated from fields) returns a message
778
+ explaining which field caused it.
779
+ """
780
+ inner = unwrap_readonly(typ)
781
+ if isinstance(inner, OwnType):
782
+ inner = inner.wrapped
783
+ record = self.registry.get_record_for_type(inner)
784
+ if record is None:
785
+ return f"non-copyable type '{typ}'"
786
+ # Walk fields to find the nocopy one (implicitly propagated)
787
+ for f in record.fields:
788
+ if self.is_type_nocopy(f.type):
789
+ return (
790
+ f"non-copyable type '{typ}' (field '{f.name}' "
791
+ f"has non-copyable type '{f.type}')"
792
+ )
793
+ # Check type arguments for generic instantiations
794
+ if isinstance(inner, NominalType) and inner.type_args:
795
+ if record is None or not record.has_copy:
796
+ for arg in inner.type_args:
797
+ if isinstance(arg, TpyType) and self.is_type_nocopy(arg):
798
+ return (
799
+ f"non-copyable type '{typ}' (type argument "
800
+ f"'{arg}' is non-copyable)"
801
+ )
802
+ # Check parents
803
+ for p in record.parents:
804
+ parent_rec = self.registry.get_record_for_type(p)
805
+ if parent_rec is not None and parent_rec.is_nocopy:
806
+ return (
807
+ f"non-copyable type '{typ}' (parent '{p}' "
808
+ f"is non-copyable)"
809
+ )
810
+ # Explicitly decorated with @nocopy (or builtin nocopy)
811
+ return f"@nocopy type '{typ}'"
812
+
813
+ def is_type_nocopy(self, typ: TpyType) -> bool:
814
+ """Check if a type is nocopy, recursively unwrapping wrappers and generics.
815
+
816
+ For generic instantiations like Box[NocopyType], checks whether any
817
+ type argument is nocopy. Respects __copy__ escape hatch: if the
818
+ containing record defines __copy__, type-arg nocopy is suppressed.
819
+ """
820
+ if isinstance(typ, ReadonlyType):
821
+ return self.is_type_nocopy(typ.wrapped)
822
+ if isinstance(typ, OwnType):
823
+ return self.is_type_nocopy(typ.wrapped)
824
+ if isinstance(typ, OptionalType):
825
+ return self.is_type_nocopy(typ.inner)
826
+ if isinstance(typ, TupleType):
827
+ for e in typ.element_types:
828
+ if isinstance(e, TpyType) and self.is_type_nocopy(e):
829
+ return True
830
+ return False
831
+ # Generic recursive alias instance: a @nocopy alternative makes the
832
+ # wrapper's std::variant non-copyable. Mirrors the TupleType walk
833
+ # above; uses the unified alternatives accessor. The recursive
834
+ # alternative (e.g. `list[Tree[T]]`) re-enters Tree[T] -- conservative
835
+ # False at the recursion point matches `is_value_type`'s guard.
836
+ if isinstance(typ, RecursiveAliasInstanceType):
837
+ key = (typ.qname, typ.type_args)
838
+ if key in self._evaluating_alias_nocopy:
839
+ return False
840
+ self._evaluating_alias_nocopy.add(key)
841
+ try:
842
+ for m in (recursive_union_alternatives(typ) or ()):
843
+ if isinstance(m, TpyType) and self.is_type_nocopy(m):
844
+ return True
845
+ return False
846
+ finally:
847
+ self._evaluating_alias_nocopy.discard(key)
848
+ record = self.registry.get_record_for_type(typ)
849
+ if record is not None and record.is_nocopy:
850
+ return True
851
+ if isinstance(typ, NominalType) and typ.type_args:
852
+ if record is not None and record.has_copy:
853
+ return False
854
+ for arg in typ.type_args:
855
+ if isinstance(arg, TpyType) and self.is_type_nocopy(arg):
856
+ return True
857
+ return False
858
+
859
+ def is_type_non_copyable(self, typ: TpyType) -> bool:
860
+ """Check if a type cannot be copied at C++ level.
861
+
862
+ Superset of is_type_nocopy: also counts records with __del__ (which
863
+ deletes copy ops in the generated struct), and walks the parent
864
+ chain for @nocopy or __del__ (which implicitly deletes copy in
865
+ the child). Propagates through record fields and type args.
866
+ Respects __copy__ escape hatch.
867
+
868
+ Used to upgrade "copies X into field/container" warnings to errors --
869
+ the generated C++ would otherwise hit a deleted copy ctor.
870
+
871
+ Kept separate from is_type_nocopy because the latter feeds into
872
+ analyzer-level `is_nocopy` propagation and user-facing "@nocopy"
873
+ diagnostics, where the narrower "user declared intent" meaning is
874
+ wanted. This helper is strictly about "will C++ reject the copy?".
875
+ """
876
+ if isinstance(typ, ReadonlyType):
877
+ return self.is_type_non_copyable(typ.wrapped)
878
+ if isinstance(typ, OwnType):
879
+ return self.is_type_non_copyable(typ.wrapped)
880
+ if isinstance(typ, OptionalType):
881
+ return self.is_type_non_copyable(typ.inner)
882
+ if isinstance(typ, TupleType):
883
+ for e in typ.element_types:
884
+ if isinstance(e, TpyType) and self.is_type_non_copyable(e):
885
+ return True
886
+ return False
887
+ # Mirrors the is_type_nocopy branch: a non-copyable alternative
888
+ # propagates through the wrapper's std::variant. Re-entry returns
889
+ # False to match the value/nocopy recursion guards (the recursive
890
+ # alternative re-enters the same instance).
891
+ if isinstance(typ, RecursiveAliasInstanceType):
892
+ key = (typ.qname, typ.type_args)
893
+ if key in self._evaluating_alias_nocopy:
894
+ return False
895
+ self._evaluating_alias_nocopy.add(key)
896
+ try:
897
+ for m in (recursive_union_alternatives(typ) or ()):
898
+ if isinstance(m, TpyType) and self.is_type_non_copyable(m):
899
+ return True
900
+ return False
901
+ finally:
902
+ self._evaluating_alias_nocopy.discard(key)
903
+ # Abstract @dynamic protocol bases have pure virtuals and the
904
+ # concrete size depends on the dynamic type, so they have no usable
905
+ # copy/move ctor at the C++ level.
906
+ if is_dyn_protocol(typ):
907
+ return True
908
+ record = self.registry.get_record_for_type(typ)
909
+ if record is None:
910
+ return False
911
+ if record.has_copy:
912
+ return False
913
+ if record.is_nocopy or record.has_del:
914
+ return True
915
+ # Inheritance: recurse into each direct parent so each parent's own logic
916
+ # (including its has_copy barrier up its chain) applies independently.
917
+ # For multi-base, any direct parent being non-copyable makes the child
918
+ # non-copyable; for single-parent, parent.has_copy correctly short-circuits
919
+ # the parent's recursion, matching the old `break`-on-has_copy behavior.
920
+ for p in record.parents:
921
+ if self.is_type_non_copyable(p):
922
+ return True
923
+ if isinstance(typ, NominalType) and typ.type_args:
924
+ for arg in typ.type_args:
925
+ if isinstance(arg, TpyType) and self.is_type_non_copyable(arg):
926
+ return True
927
+ for f in record.fields:
928
+ if self.is_type_non_copyable(f.type):
929
+ return True
930
+ return False
931
+
932
+ def get_expr_type(self, expr: TpyExpr) -> TpyType | None:
933
+ """Get the cached type of an expression, stripping Ref and Own.
934
+
935
+ Ref/Own are internal annotations for reference provenance and
936
+ ownership; callers that need bare types (codegen, type resolution,
937
+ narrowing) should not see them. ReadonlyType is preserved because
938
+ codegen needs it for const emission. The assignment handler uses
939
+ the analyze_expr return value directly (which preserves Ref/Own)
940
+ for copy-warning detection.
941
+ """
942
+ typ = self.expr_types.get(id(expr))
943
+ if typ is None:
944
+ return None
945
+ typ = unwrap_ref_type(typ)
946
+ if isinstance(typ, OwnType):
947
+ typ = typ.wrapped
948
+ return typ
949
+
950
+ def get_raw_expr_type(self, expr: TpyExpr) -> TpyType | None:
951
+ """Get the cached type of an expression WITHOUT stripping qualifiers.
952
+
953
+ Used by copy-warning detection to see Ref/Own qualifiers.
954
+ """
955
+ return self.expr_types.get(id(expr))
956
+
957
+ def set_expr_type(self, expr: TpyExpr, typ: TpyType) -> None:
958
+ """Cache the type of an expression."""
959
+ self.expr_types[id(expr)] = typ
960
+
961
+ def default_int_for_literal(
962
+ self,
963
+ typ: TpyType,
964
+ warn_node: TpyExpr | TpyStmt | None = None,
965
+ ) -> TpyType:
966
+ """Resolve configured default-int, with range-safe fallback for literals."""
967
+ if not isinstance(typ, IntLiteralType):
968
+ return self.default_int_type
969
+ default_tr = int_traits_of(self.default_int_type)
970
+ if (
971
+ default_tr is not None
972
+ and typ.value is not None
973
+ and not (default_tr.min_value <= typ.value <= default_tr.max_value)
974
+ ):
975
+ if warn_node is not None:
976
+ self.warning(
977
+ f"Integer literal {typ.value} is outside default {self.default_int_type} range; "
978
+ "inferring int (BigInt).",
979
+ warn_node,
980
+ )
981
+ return BIGINT
982
+ return self.default_int_type
983
+
984
+ def reset_function_tracking(self) -> None:
985
+ """Reset all per-function tracking state."""
986
+ self.func = FunctionTrackingState()
987
+
988
+ def save_function_state(self) -> FunctionTrackingState:
989
+ """Snapshot per-function state (for nested def isolation)."""
990
+ return deepcopy(self.func)
991
+
992
+ def restore_function_state(self, saved: FunctionTrackingState) -> None:
993
+ """Restore per-function state from a snapshot."""
994
+ self.func = saved
995
+
996
+ @contextmanager
997
+ def trial_scope(self) -> Iterator[None]:
998
+ """Snapshot/restore for tentative semantic analysis (e.g. per-candidate
999
+ lambda body trial under Regime C overload resolution).
1000
+
1001
+ Wraps a body of analysis work whose state changes must be rolled back
1002
+ unconditionally on exit -- both on success and on exception.
1003
+
1004
+ Snapshotted surfaces:
1005
+
1006
+ - Per-function state (FunctionTrackingState) -- includes call_edges,
1007
+ mutated/struct/addr-escape/returned param sets, self-mutation flags,
1008
+ borrow tracker, definitely_assigned, narrowed_types, etc. Deep-copied
1009
+ since FunctionTrackingState contains nested mutable containers.
1010
+ - Module-level type cache (``expr_types``) and the literal/view
1011
+ counters and registries (``literal_counter``, ``list_literals``,
1012
+ ``dict_literals``, ``set_literals``, ``pending_generic_counter``,
1013
+ ``str_var_counter``, ``str_vars``, ``bytes_var_counter``,
1014
+ ``bytes_vars``).
1015
+ - The diagnostics list -- truncated to its pre-trial length so
1016
+ warnings/errors emitted during a rejected trial don't leak into
1017
+ the user-visible output.
1018
+
1019
+ Lambda AST mutations (``inferred_param_types``, ``inferred_return_type``,
1020
+ ``captured_names``, ``captures_by_value``) are the caller's
1021
+ responsibility -- save and restore them around the trial. They are
1022
+ not part of SemanticContext state.
1023
+ """
1024
+ saved_func = self.save_function_state()
1025
+ saved_expr_types = dict(self.expr_types)
1026
+ saved_literal_counter = self.literal_counter
1027
+ saved_list_literals = dict(self.list_literals)
1028
+ saved_dict_literals = dict(self.dict_literals)
1029
+ saved_set_literals = dict(self.set_literals)
1030
+ saved_pending_generic_counter = self.pending_generic_counter
1031
+ saved_str_var_counter = self.str_var_counter
1032
+ saved_str_vars = dict(self.str_vars)
1033
+ saved_bytes_var_counter = self.bytes_var_counter
1034
+ saved_bytes_vars = dict(self.bytes_vars)
1035
+ saved_diagnostics_len = len(self.diagnostics)
1036
+ try:
1037
+ yield
1038
+ finally:
1039
+ self.restore_function_state(saved_func)
1040
+ self.expr_types.clear()
1041
+ self.expr_types.update(saved_expr_types)
1042
+ self.literal_counter = saved_literal_counter
1043
+ self.list_literals.clear()
1044
+ self.list_literals.update(saved_list_literals)
1045
+ self.dict_literals.clear()
1046
+ self.dict_literals.update(saved_dict_literals)
1047
+ self.set_literals.clear()
1048
+ self.set_literals.update(saved_set_literals)
1049
+ self.pending_generic_counter = saved_pending_generic_counter
1050
+ self.str_var_counter = saved_str_var_counter
1051
+ self.str_vars.clear()
1052
+ self.str_vars.update(saved_str_vars)
1053
+ self.bytes_var_counter = saved_bytes_var_counter
1054
+ self.bytes_vars.clear()
1055
+ self.bytes_vars.update(saved_bytes_vars)
1056
+ del self.diagnostics[saved_diagnostics_len:]
1057
+
1058
+ def mark_loop_var_mutated(self, name: str) -> None:
1059
+ """Mark a for-each loop variable as mutated (prevents const-ref binding)."""
1060
+ if name in self.func.loop_vars:
1061
+ self.func.mutated_loop_vars.add(name)
1062
+
1063
+ def mark_loop_var_consumed(self, name: str) -> None:
1064
+ """Mark a for-each loop variable as consumed (copied into owned storage).
1065
+
1066
+ This triggers auto-consuming iteration when the container is at last
1067
+ use, so elements are moved instead of copied.
1068
+ """
1069
+ if name in self.func.loop_vars:
1070
+ self.func.consumed_loop_vars.add(name)
1071
+
1072
+ def mark_param_mutated(self, name: str) -> None:
1073
+ """Mark a function parameter as directly mutated (Phase 1 of mutation inference).
1074
+
1075
+ For 'self': sets current_self_mutated (method self-mutation tracking).
1076
+ For regular params: adds to current_mutated_param_names.
1077
+ Also traces loop variables back to their source iterables, and traces
1078
+ element/field/ptr borrows back to their source storage (8a.5: deferred
1079
+ marking for element refs -- when v = items[i] and v.field is written,
1080
+ the write propagates back to items).
1081
+ """
1082
+ if name == "self":
1083
+ self.func.current_self_mutated = True
1084
+ return
1085
+ if name in self.func.current_param_names and name not in self.func.current_rebound_params:
1086
+ self.func.current_mutated_param_names.add(name)
1087
+ iterable = self.func.loop_var_iterable.get(name)
1088
+ if iterable is not None:
1089
+ # Field-path iterables ("c.items") need root extraction for param lookup
1090
+ self.mark_param_mutated(_storage_root(iterable))
1091
+ # 8a.5: trace through element/field/ptr borrows to source param.
1092
+ # When v = items[i] (deferred) and v is later written through,
1093
+ # mark the ultimate storage root (e.g. items) as mutated.
1094
+ # The recursive call terminates because effective_storage_through_borrows
1095
+ # on the ultimate root returns itself (no upstream borrow points to it).
1096
+ ultimate = self.func.borrow_tracker.effective_storage_through_borrows(name)
1097
+ if ultimate != name:
1098
+ self.mark_param_mutated(ultimate)
1099
+
1100
+ def mark_param_structurally_mutated(self, name: str) -> None:
1101
+ """Mark a parameter as structurally mutated (append/insert/clear/del/etc.).
1102
+
1103
+ Structural mutations invalidate element references -- this is separate from
1104
+ mark_param_mutated which also fires for element-ref taking (a = items[0]).
1105
+ Traces loop variables back to their source iterables transitively.
1106
+ """
1107
+ if name == "self":
1108
+ self.func.current_self_struct_mutated = True
1109
+ return
1110
+ if name in self.func.current_param_names and name not in self.func.current_rebound_params:
1111
+ self.func.current_struct_mutated_param_names.add(name)
1112
+ iterable = self.func.loop_var_iterable.get(name)
1113
+ if iterable is not None:
1114
+ self.mark_param_structurally_mutated(_storage_root(iterable))
1115
+
1116
+ def mark_param_returned(self, name: str) -> None:
1117
+ """Mark a parameter as contributing to the return value (8b).
1118
+
1119
+ Called when returning a reference derived from param storage, so we
1120
+ can record return_borrows_from on FunctionInfo. Mirrors mark_param_mutated
1121
+ but writes to current_returned_param_names instead.
1122
+ Traces loop variables back to their source iterables transitively.
1123
+ """
1124
+ if name == "self":
1125
+ self.func.current_returned_param_names.add("self")
1126
+ return
1127
+ if name in self.func.current_param_names and name not in self.func.current_rebound_params:
1128
+ self.func.current_returned_param_names.add(name)
1129
+ iterable = self.func.loop_var_iterable.get(name)
1130
+ if iterable is not None:
1131
+ self.mark_param_returned(_storage_root(iterable))
1132
+
1133
+ def mark_own_param_consumed(self, name: str) -> None:
1134
+ """Mark an Own[T] param as consumed (stored, forwarded, or returned)."""
1135
+ if name in self.func.current_param_names:
1136
+ self.func.current_consumed_own_params.add(name)
1137
+
1138
+ # ------------------------------------------------------------------
1139
+ # View-type family generic accessors
1140
+ # ------------------------------------------------------------------
1141
+
1142
+ def view_var_map(self, family: ViewTypeFamily) -> dict[str, int]:
1143
+ """Variable-name -> var_id mapping for the given family."""
1144
+ if family.pending_type_class is PendingStrType:
1145
+ return self.func.variable_to_str_var
1146
+ return self.func.variable_to_bytes_var
1147
+
1148
+ def view_source_borrows_map(self, family: ViewTypeFamily) -> dict[str, set[int]]:
1149
+ """Source-storage -> set of borrowing var_ids for the given family."""
1150
+ if family.pending_type_class is PendingStrType:
1151
+ return self.func.str_source_borrows
1152
+ return self.func.bytes_source_borrows
1153
+
1154
+ def view_pending_resolutions(self, family: ViewTypeFamily) -> list[int]:
1155
+ """Pending resolution list for the given family."""
1156
+ if family.pending_type_class is PendingStrType:
1157
+ return self.func.pending_str_resolutions
1158
+ return self.func.pending_bytes_resolutions
1159
+
1160
+ def view_vars(self, family: ViewTypeFamily) -> dict[int, ViewVarInfo]:
1161
+ """Var-id -> ViewVarInfo registry for the given family."""
1162
+ if family.pending_type_class is PendingStrType:
1163
+ return self.str_vars
1164
+ return self.bytes_vars
1165
+
1166
+ def next_view_var_id(self, family: ViewTypeFamily) -> int:
1167
+ """Allocate and return the next var_id for the given family."""
1168
+ if family.pending_type_class is PendingStrType:
1169
+ vid = self.str_var_counter
1170
+ self.str_var_counter += 1
1171
+ return vid
1172
+ vid = self.bytes_var_counter
1173
+ self.bytes_var_counter += 1
1174
+ return vid
1175
+
1176
+ # ------------------------------------------------------------------
1177
+ # View-type mutation tracking
1178
+ # ------------------------------------------------------------------
1179
+
1180
+ def mark_view_borrowers_mutated(self, storage: str, family: ViewTypeFamily) -> None:
1181
+ """Mark pending view-type borrowers of storage as source-mutated.
1182
+
1183
+ Called at mutation sites so that view resolution falls back to
1184
+ the owned type when the view's source storage is mutated.
1185
+ Also invalidates field-path borrows: mutating ``p`` invalidates
1186
+ views borrowed from ``p.name``, ``p.field``, etc.
1187
+ """
1188
+ source_borrows = self.view_source_borrows_map(family)
1189
+ vars_registry = self.view_vars(family)
1190
+ keys = [storage]
1191
+ prefix = storage + "."
1192
+ keys.extend(k for k in source_borrows if k.startswith(prefix))
1193
+ for key in keys:
1194
+ var_ids = source_borrows.get(key)
1195
+ if not var_ids:
1196
+ continue
1197
+ for var_id in var_ids:
1198
+ info = vars_registry.get(var_id)
1199
+ if info is not None:
1200
+ info.source_mutated = True
1201
+
1202
+ def mark_all_view_borrowers_mutated(self, storage: str) -> None:
1203
+ """Mark borrowers across all view-type families as source-mutated."""
1204
+ for family in VIEW_TYPE_FAMILIES:
1205
+ self.mark_view_borrowers_mutated(storage, family)
1206
+
1207
+ # ------------------------------------------------------------------
1208
+ # Unified container literal lookup
1209
+ # ------------------------------------------------------------------
1210
+
1211
+ def get_container_info(self, literal_id: int) -> ListLiteralInfo | DictLiteralInfo | SetLiteralInfo | None:
1212
+ """Look up container info across all container types by literal_id."""
1213
+ return (self.list_literals.get(literal_id)
1214
+ or self.dict_literals.get(literal_id)
1215
+ or self.set_literals.get(literal_id))
1216
+
1217
+ def track_container_variable(
1218
+ self,
1219
+ var_name: str,
1220
+ pending_type: 'PendingListType | PendingDictType | PendingSetType',
1221
+ decl_line: int | None,
1222
+ ) -> None:
1223
+ """Register var-name -> literal_id mapping for any pending container type.
1224
+
1225
+ Also sets variable_name and decl_line on the corresponding LiteralInfo.
1226
+ Single code path for list/dict/set -- adding a new container type means
1227
+ adding one branch here instead of duplicating blocks in statements.py.
1228
+ """
1229
+ literal_id = pending_type.literal_id
1230
+ info: ListLiteralInfo | DictLiteralInfo | SetLiteralInfo
1231
+ if isinstance(pending_type, PendingListType):
1232
+ self.func.variable_to_literal[var_name] = literal_id
1233
+ info = self.list_literals[literal_id]
1234
+ elif isinstance(pending_type, PendingDictType):
1235
+ self.func.variable_to_dict_literal[var_name] = literal_id
1236
+ info = self.dict_literals[literal_id]
1237
+ elif isinstance(pending_type, PendingSetType):
1238
+ self.func.variable_to_set_literal[var_name] = literal_id
1239
+ info = self.set_literals[literal_id]
1240
+ else:
1241
+ return
1242
+ info.variable_name = var_name
1243
+ info.decl_line = decl_line