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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
  2. tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
  3. tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
  4. tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
  5. tpyc/__init__.py +104 -0
  6. tpyc/__main__.py +6 -0
  7. tpyc/_buildinfo.py +1 -0
  8. tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
  9. tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
  10. tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
  11. tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
  12. tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
  13. tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
  14. tpyc/_data/lib/tpy/_functools_macros.py +80 -0
  15. tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
  16. tpyc/_data/lib/tpy/argparse.py +2062 -0
  17. tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
  18. tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
  19. tpyc/_data/lib/tpy/base64.py +410 -0
  20. tpyc/_data/lib/tpy/bisect.py +39 -0
  21. tpyc/_data/lib/tpy/builtins.py +38 -0
  22. tpyc/_data/lib/tpy/dataclasses.py +354 -0
  23. tpyc/_data/lib/tpy/enum.py +23 -0
  24. tpyc/_data/lib/tpy/functools.py +33 -0
  25. tpyc/_data/lib/tpy/hashlib.py +206 -0
  26. tpyc/_data/lib/tpy/heapq.py +118 -0
  27. tpyc/_data/lib/tpy/io.py +395 -0
  28. tpyc/_data/lib/tpy/json.py +221 -0
  29. tpyc/_data/lib/tpy/math.py +406 -0
  30. tpyc/_data/lib/tpy/random.py +597 -0
  31. tpyc/_data/lib/tpy/re.py +467 -0
  32. tpyc/_data/lib/tpy/socket.py +379 -0
  33. tpyc/_data/lib/tpy/struct.py +178 -0
  34. tpyc/_data/lib/tpy/sys.py +40 -0
  35. tpyc/_data/lib/tpy/time.py +39 -0
  36. tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
  37. tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
  38. tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
  39. tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
  40. tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
  41. tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
  42. tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
  43. tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
  44. tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
  45. tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
  46. tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
  47. tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
  48. tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
  49. tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
  50. tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
  51. tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
  52. tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
  53. tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
  54. tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
  55. tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
  56. tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
  57. tpyc/_data/lib/tpy/tpy/_version.py +29 -0
  58. tpyc/_data/lib/tpy/tpy/bits.py +28 -0
  59. tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
  60. tpyc/_data/lib/tpy/tpy/extern.py +8 -0
  61. tpyc/_data/lib/tpy/tpy/mem.py +49 -0
  62. tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
  63. tpyc/_data/lib/tpy/tpy/version.py +21 -0
  64. tpyc/_data/lib/tpy/typing.py +13 -0
  65. tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
  66. tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
  67. tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
  68. tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
  69. tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
  70. tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
  71. tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
  72. tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
  73. tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
  74. tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
  75. tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
  76. tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
  77. tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
  78. tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
  79. tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
  80. tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
  81. tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
  82. tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
  83. tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
  84. tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
  85. tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
  86. tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
  87. tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
  88. tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
  89. tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
  90. tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
  91. tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
  92. tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
  93. tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
  94. tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
  95. tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
  96. tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
  97. tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
  98. tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
  99. tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
  100. tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
  101. tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
  102. tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
  103. tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
  104. tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
  105. tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
  106. tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
  107. tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
  108. tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
  109. tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
  110. tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
  111. tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
  112. tpyc/_data/runtime/cpp/third_party/README.md +58 -0
  113. tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
  114. tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
  115. tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
  116. tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
  117. tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
  118. tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
  119. tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
  120. tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
  121. tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
  122. tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
  123. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
  124. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
  125. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
  126. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
  127. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
  128. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
  129. tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
  130. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
  131. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
  132. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
  133. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
  134. tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
  135. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
  136. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
  137. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
  138. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
  139. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
  140. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
  141. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
  142. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
  143. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
  144. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
  145. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
  146. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
  147. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
  148. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
  149. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
  150. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
  151. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
  152. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
  153. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
  154. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
  155. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
  156. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
  157. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
  158. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
  159. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
  160. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
  161. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
  162. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
  163. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
  164. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
  165. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
  166. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
  167. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
  168. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
  169. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
  170. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
  171. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
  172. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
  173. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
  174. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
  175. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
  176. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
  177. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
  178. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
  179. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
  180. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
  181. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
  182. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
  183. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
  184. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
  185. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
  186. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
  187. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
  188. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
  189. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
  190. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
  191. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
  192. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
  193. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
  194. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
  195. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
  196. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
  197. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
  198. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
  199. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
  200. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
  201. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
  202. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
  203. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
  204. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
  205. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
  206. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
  207. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
  208. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
  209. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
  210. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
  211. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
  212. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
  213. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
  214. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
  215. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
  216. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
  217. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
  218. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
  219. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
  220. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
  221. tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
  222. tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
  223. tpyc/build/__init__.py +7 -0
  224. tpyc/build/pcre2.py +122 -0
  225. tpyc/build/third_party.py +413 -0
  226. tpyc/cli.py +822 -0
  227. tpyc/codegen_cpp/__init__.py +18 -0
  228. tpyc/codegen_cpp/builtins.py +484 -0
  229. tpyc/codegen_cpp/context.py +2064 -0
  230. tpyc/codegen_cpp/expressions.py +5940 -0
  231. tpyc/codegen_cpp/functions.py +1913 -0
  232. tpyc/codegen_cpp/gen_async.py +3258 -0
  233. tpyc/codegen_cpp/gen_generators.py +657 -0
  234. tpyc/codegen_cpp/generator.py +2258 -0
  235. tpyc/codegen_cpp/match.py +1997 -0
  236. tpyc/codegen_cpp/param_const.py +172 -0
  237. tpyc/codegen_cpp/protocols.py +907 -0
  238. tpyc/codegen_cpp/records.py +1654 -0
  239. tpyc/codegen_cpp/resumable_cfg.py +1651 -0
  240. tpyc/codegen_cpp/statements.py +4963 -0
  241. tpyc/codegen_cpp/string_dispatch.py +76 -0
  242. tpyc/codegen_cpp/test_context.py +46 -0
  243. tpyc/codegen_cpp/test_param_const.py +113 -0
  244. tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
  245. tpyc/codegen_cpp/type_resolution.py +53 -0
  246. tpyc/codegen_cpp/types.py +436 -0
  247. tpyc/codegen_cpp/variant_access.py +135 -0
  248. tpyc/coercions.py +749 -0
  249. tpyc/compilation_context.py +57 -0
  250. tpyc/compiler.py +3945 -0
  251. tpyc/cycle_detection.py +358 -0
  252. tpyc/diagnostics.py +135 -0
  253. tpyc/dump_types.py +353 -0
  254. tpyc/frontend_diagnostics.py +47 -0
  255. tpyc/frontend_ir/__init__.py +140 -0
  256. tpyc/frontend_ir/lower.py +1098 -0
  257. tpyc/frontend_ir/nodes.py +718 -0
  258. tpyc/frontend_ir/resolver_adapter.py +151 -0
  259. tpyc/frontend_plugin.py +209 -0
  260. tpyc/install_docs.py +81 -0
  261. tpyc/liveness.py +756 -0
  262. tpyc/macro_api.py +1724 -0
  263. tpyc/macro_loader.py +497 -0
  264. tpyc/module_names.py +64 -0
  265. tpyc/modules/__init__.py +31 -0
  266. tpyc/modules/defs.py +89 -0
  267. tpyc/modules/registry.py +36 -0
  268. tpyc/modules/resolver.py +192 -0
  269. tpyc/modules/type_resolution.py +629 -0
  270. tpyc/namespace.py +172 -0
  271. tpyc/parse/__init__.py +84 -0
  272. tpyc/parse/imports.py +490 -0
  273. tpyc/parse/nodes.py +1732 -0
  274. tpyc/parse/parser.py +4043 -0
  275. tpyc/parse/resolve_refs.py +466 -0
  276. tpyc/parse/type_resolver.py +1060 -0
  277. tpyc/prescan.py +254 -0
  278. tpyc/qnames.py +149 -0
  279. tpyc/repl.py +529 -0
  280. tpyc/repl_backends.py +848 -0
  281. tpyc/sema/__init__.py +21 -0
  282. tpyc/sema/analyzer.py +3625 -0
  283. tpyc/sema/bound_check.py +72 -0
  284. tpyc/sema/builder_trace.py +684 -0
  285. tpyc/sema/calls.py +5406 -0
  286. tpyc/sema/compatibility.py +2107 -0
  287. tpyc/sema/context.py +1243 -0
  288. tpyc/sema/expressions.py +3737 -0
  289. tpyc/sema/flow_facts.py +199 -0
  290. tpyc/sema/init_tracker.py +150 -0
  291. tpyc/sema/list_literals.py +69 -0
  292. tpyc/sema/literal_utils.py +27 -0
  293. tpyc/sema/local_deduction.py +1088 -0
  294. tpyc/sema/macros.py +179 -0
  295. tpyc/sema/match.py +1177 -0
  296. tpyc/sema/method_expansion.py +347 -0
  297. tpyc/sema/methods.py +2197 -0
  298. tpyc/sema/mutation_propagation.py +268 -0
  299. tpyc/sema/narrowing.py +857 -0
  300. tpyc/sema/numeric_lattice.py +160 -0
  301. tpyc/sema/operators.py +402 -0
  302. tpyc/sema/overloads.py +841 -0
  303. tpyc/sema/protocols.py +1209 -0
  304. tpyc/sema/reach_analysis.py +202 -0
  305. tpyc/sema/registration.py +3156 -0
  306. tpyc/sema/scope_tracker.py +193 -0
  307. tpyc/sema/statements.py +4426 -0
  308. tpyc/sema/type_ops.py +1879 -0
  309. tpyc/sema/value_range.py +181 -0
  310. tpyc/symbol_binding.py +259 -0
  311. tpyc/test_c3_mro.py +208 -0
  312. tpyc/test_cli_argv.py +52 -0
  313. tpyc/test_compiler.py +559 -0
  314. tpyc/test_contains_type_param.py +101 -0
  315. tpyc/test_cycle_detection.py +221 -0
  316. tpyc/test_dump_types.py +225 -0
  317. tpyc/test_install_docs.py +65 -0
  318. tpyc/test_local_cpp_form.py +135 -0
  319. tpyc/test_macro_loader.py +76 -0
  320. tpyc/test_method_expansion.py +254 -0
  321. tpyc/test_nominal_identity.py +182 -0
  322. tpyc/test_overloads.py +410 -0
  323. tpyc/test_parse.py +303 -0
  324. tpyc/test_parse_type_ref.py +506 -0
  325. tpyc/test_parse_version_info.py +58 -0
  326. tpyc/test_reach_analysis.py +72 -0
  327. tpyc/test_ref_type.py +216 -0
  328. tpyc/test_send_sync_substitution.py +276 -0
  329. tpyc/test_tuple_mutation_propagation.py +206 -0
  330. tpyc/test_type_def_registry.py +1729 -0
  331. tpyc/test_union_types.py +195 -0
  332. tpyc/type_def_registry.py +975 -0
  333. tpyc/typesys.py +5104 -0
@@ -0,0 +1,1098 @@
1
+ """Lowering pass: FrontendModule (IR) -> TpyModule (parser AST).
2
+
3
+ Lowering owns the IR -> TpyModule adapter and the parser-internal field
4
+ synthesis that a Python parse would normally perform (import dicts,
5
+ TpyImport emission, call-site `resolved_import` tagging).
6
+
7
+ It does NOT resolve names or types -- that is sema's job. For M1 the IR
8
+ has no type expressions, so symbolic-type lowering is stubbed.
9
+
10
+ Diagnostics emitted here use the `FrontendDiagnostic` envelope with
11
+ category `PLUGIN_IR_INVALID` (structural problem in plugin output) or
12
+ `LOWERING_INTERNAL` (compiler bug after validation passed).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from dataclasses import dataclass, field
18
+ from pathlib import Path
19
+ from typing import Iterable
20
+
21
+ from ..diagnostics import Diagnostic, DiagnosticLevel
22
+ from ..frontend_diagnostics import FrontendDiagnostic, FrontendDiagnosticCategory
23
+ from ..parse.nodes import (
24
+ ModuleDirectives,
25
+ SourceLocation,
26
+ TpyArrayLiteral,
27
+ TpyAsPattern,
28
+ TpyAssign,
29
+ TpyBinOp,
30
+ TpyCallableRef,
31
+ TpyClassPattern,
32
+ TpyBoolLiteral,
33
+ TpyBreak,
34
+ TpyCall,
35
+ TpyContinue,
36
+ TpyEnum,
37
+ TpyExprStmt,
38
+ TpyFieldAccess,
39
+ TpyFloatLiteral,
40
+ TpyForEach,
41
+ TpyFunction,
42
+ TpyIf,
43
+ TpyImport,
44
+ TpyIntLiteral,
45
+ TpyLiteralPattern,
46
+ TpyMatch,
47
+ TpyMatchCase,
48
+ TpyMethodCall,
49
+ TpyModule,
50
+ TpyName,
51
+ TpyNoneLiteral,
52
+ TpyRaise,
53
+ TpyRecord,
54
+ TpyReturn,
55
+ TpySetLiteral,
56
+ TpyStrLiteral,
57
+ TpySubscript,
58
+ TpyTypeRef,
59
+ TpyUnaryOp,
60
+ TpyUnionRef,
61
+ TpyValuePattern,
62
+ TpyVarDecl,
63
+ TpyWhile,
64
+ TpyWildcardPattern,
65
+ )
66
+ from ..typesys import FieldInfo, RecordInfo
67
+ from .resolver_adapter import make_plugin_resolver
68
+ from .nodes import (
69
+ API_VERSION,
70
+ Assign,
71
+ Attr,
72
+ BinOp,
73
+ BinOpKind,
74
+ BoolLit,
75
+ Break,
76
+ Call,
77
+ CallableType,
78
+ CmpOpKind,
79
+ Compare,
80
+ Continue,
81
+ Enum,
82
+ EnumValue,
83
+ ExprStmt,
84
+ Field,
85
+ FloatLit,
86
+ ForEach,
87
+ ForRange,
88
+ FrontendModule,
89
+ FromImport,
90
+ Function,
91
+ If,
92
+ Import,
93
+ IntLit,
94
+ IntTypeArg,
95
+ ListLit,
96
+ Loc,
97
+ Match,
98
+ MatchCase,
99
+ MatchClass,
100
+ MatchValue,
101
+ MatchWildcard,
102
+ Name,
103
+ NamedType,
104
+ NoneLit,
105
+ Param,
106
+ PointerType,
107
+ Raise,
108
+ RangeDir,
109
+ Record,
110
+ RepeatUntil,
111
+ Return,
112
+ SetLit,
113
+ StarImport,
114
+ StrLit,
115
+ Subscript,
116
+ TypeArg,
117
+ TypeExpr,
118
+ TypeTypeArg,
119
+ UnaryOp,
120
+ UnaryOpKind,
121
+ UnionType,
122
+ VarDecl,
123
+ While,
124
+ )
125
+
126
+
127
+ # Map IR operator enums onto the string opcodes TPy's parser AST uses
128
+ # (see `tpyc/parse/parser.py` -- `_BINOP_TO_STR` / `_UNARYOP_TO_STR`).
129
+ _BINOP_OP_STR: dict[BinOpKind, str] = {
130
+ BinOpKind.ADD: "+",
131
+ BinOpKind.SUB: "-",
132
+ BinOpKind.MUL: "*",
133
+ BinOpKind.TRUE_DIV: "div",
134
+ BinOpKind.FLOOR_DIV: "//",
135
+ BinOpKind.MOD: "%",
136
+ BinOpKind.POW: "**",
137
+ BinOpKind.BIT_OR: "|",
138
+ BinOpKind.BIT_XOR: "^",
139
+ BinOpKind.BIT_AND: "&",
140
+ BinOpKind.LSHIFT: "<<",
141
+ BinOpKind.RSHIFT: ">>",
142
+ BinOpKind.LOGICAL_AND: "&&",
143
+ BinOpKind.LOGICAL_OR: "||",
144
+ }
145
+
146
+ _UNARYOP_OP_STR: dict[UnaryOpKind, str] = {
147
+ UnaryOpKind.POS: "+",
148
+ UnaryOpKind.NEG: "-",
149
+ UnaryOpKind.NOT: "!",
150
+ UnaryOpKind.INVERT: "~",
151
+ }
152
+
153
+ # IR comparison enum -> parser string opcode (matches the parser's
154
+ # `_CMPOP_TO_STR` table). Used for both the single-op case (lowers to
155
+ # `TpyBinOp`) and the chained-op case (lowers to `TpyChainedCompare`).
156
+ _CMPOP_OP_STR: dict[CmpOpKind, str] = {
157
+ CmpOpKind.EQ: "==",
158
+ CmpOpKind.NE: "!=",
159
+ CmpOpKind.LT: "<",
160
+ CmpOpKind.LE: "<=",
161
+ CmpOpKind.GT: ">",
162
+ CmpOpKind.GE: ">=",
163
+ CmpOpKind.IS: "is",
164
+ CmpOpKind.IS_NOT: "is not",
165
+ CmpOpKind.IN: "in",
166
+ CmpOpKind.NOT_IN: "not in",
167
+ }
168
+
169
+
170
+ @dataclass
171
+ class LoweredFrontend:
172
+ """Result of lowering one FrontendModule.
173
+
174
+ `module` is `None` when validation failed before a partial TpyModule
175
+ could be constructed; callers should bail on the affected module.
176
+ `diagnostics` holds wrapped FrontendDiagnostics produced by lowering
177
+ plus any diagnostics the plugin returned in its FrontendOutput.
178
+ """
179
+ module: TpyModule | None
180
+ diagnostics: list[FrontendDiagnostic] = field(default_factory=list)
181
+
182
+
183
+ def lower_module(
184
+ fm: FrontendModule,
185
+ plugin_name: str,
186
+ plugin_diagnostics: Iterable[Diagnostic] = (),
187
+ *,
188
+ is_entry_point: bool = False,
189
+ ) -> LoweredFrontend:
190
+ """Lower a FrontendModule produced by a plugin to a TpyModule.
191
+
192
+ `plugin_name` is used to tag wrapped diagnostics from the plugin.
193
+ `plugin_diagnostics` come from `FrontendOutput.diagnostics`; they
194
+ are wrapped with category `PLUGIN_REPORTED` and propagated.
195
+ `is_entry_point` mirrors TPy's parser-side convention: the
196
+ entry-point module's records and enums are minted with qname prefix
197
+ `__main__` (matching sema's `ctx.module_name = "__main__"` rename)
198
+ so resolver-side placeholders agree with sema's
199
+ `attach_dynamic_type_def` registrations downstream.
200
+ """
201
+ diags: list[FrontendDiagnostic] = []
202
+
203
+ for d in plugin_diagnostics:
204
+ diags.append(FrontendDiagnostic(
205
+ diagnostic=d,
206
+ category=FrontendDiagnosticCategory.PLUGIN_REPORTED,
207
+ plugin_name=plugin_name,
208
+ source_language=fm.source_language or None,
209
+ ))
210
+ plugin_had_error = any(
211
+ fd.diagnostic.level == DiagnosticLevel.ERROR
212
+ and fd.category == FrontendDiagnosticCategory.PLUGIN_REPORTED
213
+ for fd in diags
214
+ )
215
+
216
+ # Structural validation. Failures here surface as PLUGIN_IR_INVALID.
217
+ if fm.api_version != API_VERSION:
218
+ diags.append(_ir_invalid(
219
+ plugin_name, fm,
220
+ f"FrontendModule.api_version={fm.api_version}; "
221
+ f"compiler supports api_version={API_VERSION}",
222
+ ))
223
+ return LoweredFrontend(module=None, diagnostics=diags)
224
+
225
+ if not fm.qname:
226
+ diags.append(_ir_invalid(
227
+ plugin_name, fm,
228
+ "FrontendModule.qname is empty",
229
+ ))
230
+ return LoweredFrontend(module=None, diagnostics=diags)
231
+
232
+ # Imports: build the four dicts the parser/sema expect and emit
233
+ # corresponding TpyImport statements at the head of top_level_stmts.
234
+ # `module_aliases` follows the parser convention -- canonical name
235
+ # keys the local alias -- not the reverse map used at lookup time.
236
+ module_imports: dict[str, set[tuple[str, str]] | None] = {}
237
+ user_module_imports: dict[str, int] = {}
238
+ bare_module_imports: set[str] = set()
239
+ module_aliases: dict[str, str] = {}
240
+ name_to_origin: dict[str, tuple[str, str]] = {}
241
+ leading_imports: list[TpyImport] = []
242
+ star_imports: set[str] = set()
243
+
244
+ for imp in fm.imports:
245
+ loc = _to_source_loc(imp.loc)
246
+ lineno = loc.line if loc else 0
247
+ if isinstance(imp, Import):
248
+ if not imp.module:
249
+ diags.append(_ir_invalid(
250
+ plugin_name, fm, "Import.module is empty"))
251
+ continue
252
+ if imp.module not in module_imports:
253
+ module_imports[imp.module] = set()
254
+ user_module_imports[imp.module] = lineno
255
+ bare_module_imports.add(imp.module)
256
+ if imp.alias is not None:
257
+ module_aliases[imp.module] = imp.alias
258
+ leading_imports.append(TpyImport(
259
+ module_name=imp.module, alias=imp.alias, loc=loc))
260
+ elif isinstance(imp, FromImport):
261
+ if not imp.module:
262
+ diags.append(_ir_invalid(
263
+ plugin_name, fm, "FromImport.module is empty"))
264
+ continue
265
+ bucket = module_imports.setdefault(imp.module, set())
266
+ assert isinstance(bucket, set) # never None for FromImport
267
+ for nm in imp.names:
268
+ bucket.add((nm.original, nm.local))
269
+ name_to_origin[nm.local] = (imp.module, nm.original)
270
+ user_module_imports[imp.module] = lineno
271
+ leading_imports.append(TpyImport(
272
+ module_name=imp.module, loc=loc))
273
+ elif isinstance(imp, StarImport):
274
+ if not imp.module:
275
+ diags.append(_ir_invalid(
276
+ plugin_name, fm, "StarImport.module is empty"))
277
+ continue
278
+ # The compiler's `_expand_star_imports_for_module` consumes
279
+ # `star_imports` + `module_imports[mod] = set()` and fans
280
+ # the imported module's public surface into the importer.
281
+ # Implicit-stdlib star imports (`tpy` / `builtins` /
282
+ # `typing`) are handled at parse time by the .py parser
283
+ # itself; plugin lowering doesn't take that path because
284
+ # source-language plugins would name a non-implicit
285
+ # module here.
286
+ module_imports.setdefault(imp.module, set())
287
+ user_module_imports[imp.module] = lineno
288
+ star_imports.add(imp.module)
289
+ leading_imports.append(TpyImport(
290
+ module_name=imp.module, loc=loc))
291
+ else:
292
+ diags.append(_ir_invalid(
293
+ plugin_name, fm,
294
+ f"unknown import node kind: {type(imp).__name__}",
295
+ ))
296
+
297
+ # Build the parser-adapter / TypeResolver early so we can register
298
+ # user records into the adapter's registry before lowering any
299
+ # bodies that might reference them. The same TypeResolver the .py
300
+ # parser uses runs over our adapter -- it reads parser-internal
301
+ # attributes/methods exposed on the adapter object.
302
+ resolver = make_plugin_resolver(
303
+ module_name=fm.qname,
304
+ imports=dict(module_imports),
305
+ name_index=tuple(
306
+ (local, mod, original)
307
+ for local, (mod, original) in name_to_origin.items()
308
+ ),
309
+ )
310
+
311
+ # Records: lower each and register the resulting RecordInfo into
312
+ # the resolver's registry so type references downstream (var decls,
313
+ # function params, other records) can resolve user record names.
314
+ # For the entry point we mint qnames against `__main__`; sema
315
+ # follows the same rule for the entry module, so this keeps
316
+ # parser-side and sema-side qnames in lockstep.
317
+ qname_prefix = "__main__" if is_entry_point else fm.qname
318
+ tpy_records: list[TpyRecord] = []
319
+ record_class_names: set[str] = set()
320
+ for rec in fm.records:
321
+ lowered_rec, rinfo = _lower_record(
322
+ rec, plugin_name, fm, diags, qname_prefix)
323
+ if lowered_rec is None or rinfo is None:
324
+ continue
325
+ tpy_records.append(lowered_rec)
326
+ resolver.registry.register_record(rinfo)
327
+ record_class_names.add(rec.name)
328
+ if record_class_names:
329
+ # Adapter exposes a `frozenset` here; merge with whatever the
330
+ # resolver-adapter constructor pre-populated (currently empty).
331
+ resolver._parser._module_class_names = frozenset(
332
+ resolver._parser._module_class_names | record_class_names
333
+ )
334
+
335
+ # Enums: lower each and register a placeholder NominalType so the
336
+ # resolver can route `EnumName` and `EnumName.Member` references.
337
+ # Sema's `register_enum` re-registers later with the fully-populated
338
+ # NominalType + TypeDef.enum payload (members, underlying type, ...).
339
+ tpy_enums: list[TpyEnum] = []
340
+ for en in fm.enums:
341
+ lowered_en = _lower_enum(en, plugin_name, fm, diags)
342
+ if lowered_en is None:
343
+ continue
344
+ tpy_enums.append(lowered_en)
345
+ resolver.registry.register_enum_placeholder(
346
+ en.name, module=qname_prefix,
347
+ )
348
+
349
+ # Functions: lower each to a TpyFunction. Function bodies do not
350
+ # share the module-level statement list, so lowering them before
351
+ # walking top_level_stmts keeps things tidy.
352
+ tpy_functions: list[TpyFunction] = []
353
+ for fn in fm.functions:
354
+ lowered_fn = _lower_function(fn, name_to_origin, plugin_name, fm, diags)
355
+ if lowered_fn is not None:
356
+ tpy_functions.append(lowered_fn)
357
+
358
+ # Statements
359
+ top_level_stmts = list(leading_imports)
360
+ for stmt in fm.top_level_stmts:
361
+ lowered = _lower_stmt(stmt, name_to_origin, plugin_name, fm, diags)
362
+ if lowered is not None:
363
+ top_level_stmts.append(lowered)
364
+
365
+ # Directives: map FrontendDirectives onto ModuleDirectives.
366
+ directives = ModuleDirectives(
367
+ includes=list(fm.directives.cpp_includes),
368
+ link_libs=list(fm.directives.link_libs),
369
+ third_party_deps=list(fm.directives.third_party_deps),
370
+ native_module=fm.directives.native_module,
371
+ cpp_namespace=fm.directives.cpp_namespace,
372
+ )
373
+
374
+ has_ir_error = any(
375
+ fd.category == FrontendDiagnosticCategory.PLUGIN_IR_INVALID
376
+ for fd in diags
377
+ )
378
+ if has_ir_error or plugin_had_error:
379
+ return LoweredFrontend(module=None, diagnostics=diags)
380
+
381
+ module = TpyModule(
382
+ records=tpy_records,
383
+ functions=tpy_functions,
384
+ protocols=[],
385
+ enums=tpy_enums,
386
+ top_level_stmts=top_level_stmts,
387
+ source_lines=list(fm.source_lines),
388
+ imports=module_imports,
389
+ user_module_imports=user_module_imports,
390
+ module_aliases=module_aliases,
391
+ bare_module_imports=bare_module_imports,
392
+ star_imports=star_imports,
393
+ directives=directives,
394
+ resolver=resolver,
395
+ )
396
+ return LoweredFrontend(module=module, diagnostics=diags)
397
+
398
+
399
+ def _lower_stmt(
400
+ stmt,
401
+ name_to_origin: dict[str, tuple[str, str]],
402
+ plugin_name: str,
403
+ fm: FrontendModule,
404
+ diags: list[FrontendDiagnostic],
405
+ ):
406
+ loc = _to_source_loc(stmt.loc) if getattr(stmt, "loc", None) else None
407
+ if isinstance(stmt, ExprStmt):
408
+ expr = _lower_expr(stmt.value, name_to_origin, plugin_name, fm, diags)
409
+ if expr is None:
410
+ return None
411
+ out = TpyExprStmt(expr=expr)
412
+ out.loc = loc
413
+ return out
414
+ if isinstance(stmt, VarDecl):
415
+ type_ref = (_lower_type(stmt.type, plugin_name, fm, diags)
416
+ if stmt.type is not None else None)
417
+ init = (_lower_expr(stmt.init, name_to_origin, plugin_name, fm, diags)
418
+ if stmt.init is not None else None)
419
+ if stmt.init is not None and init is None:
420
+ return None
421
+ return TpyVarDecl(name=stmt.name, type=type_ref, init=init, loc=loc)
422
+ if isinstance(stmt, Assign):
423
+ if len(stmt.targets) != 1:
424
+ diags.append(_ir_invalid(
425
+ plugin_name, fm,
426
+ "Assign requires exactly one target in M2 "
427
+ "(chained assignment lands later)",
428
+ ))
429
+ return None
430
+ target = _lower_expr(stmt.targets[0], name_to_origin, plugin_name, fm, diags)
431
+ value = _lower_expr(stmt.value, name_to_origin, plugin_name, fm, diags)
432
+ if target is None or value is None:
433
+ return None
434
+ return TpyAssign(target=target, value=value, loc=loc)
435
+ if isinstance(stmt, If):
436
+ cond = _lower_expr(stmt.cond, name_to_origin, plugin_name, fm, diags)
437
+ if cond is None:
438
+ return None
439
+ then_body = _lower_stmt_list(
440
+ stmt.then_body, name_to_origin, plugin_name, fm, diags)
441
+ else_body = _lower_stmt_list(
442
+ stmt.else_body, name_to_origin, plugin_name, fm, diags)
443
+ return TpyIf(condition=cond, then_body=then_body, else_body=else_body, loc=loc)
444
+ if isinstance(stmt, While):
445
+ cond = _lower_expr(stmt.cond, name_to_origin, plugin_name, fm, diags)
446
+ if cond is None:
447
+ return None
448
+ body = _lower_stmt_list(
449
+ stmt.body, name_to_origin, plugin_name, fm, diags)
450
+ return TpyWhile(condition=cond, body=body, loc=loc)
451
+ if isinstance(stmt, RepeatUntil):
452
+ # Desugar to `while True: body; if cond: break`. Pascal's
453
+ # `repeat...until` runs the body at least once and exits when
454
+ # cond becomes true; the `while True` shape preserves that.
455
+ cond = _lower_expr(stmt.cond, name_to_origin, plugin_name, fm, diags)
456
+ if cond is None:
457
+ return None
458
+ body = _lower_stmt_list(
459
+ stmt.body, name_to_origin, plugin_name, fm, diags)
460
+ body.append(TpyIf(
461
+ condition=cond,
462
+ then_body=[TpyBreak(loc=loc)],
463
+ else_body=[],
464
+ loc=loc,
465
+ ))
466
+ return TpyWhile(
467
+ condition=TpyBoolLiteral(value=True, loc=loc),
468
+ body=body, loc=loc,
469
+ )
470
+ if isinstance(stmt, ForRange):
471
+ # Desugar to `for var in range(start, end[+1], [-1]): body`.
472
+ # `inclusive=True` (Pascal default) bumps the endpoint by one
473
+ # so the upper bound is visited; descending loops use a step
474
+ # of -1 and bump the endpoint by -1 instead.
475
+ start = _lower_expr(stmt.start, name_to_origin, plugin_name, fm, diags)
476
+ end = _lower_expr(stmt.end, name_to_origin, plugin_name, fm, diags)
477
+ if start is None or end is None:
478
+ return None
479
+ bump = 1 if stmt.direction == RangeDir.ASC else -1
480
+ if stmt.inclusive:
481
+ end = TpyBinOp(
482
+ left=end,
483
+ op="+" if bump > 0 else "-",
484
+ right=TpyIntLiteral(value=1, loc=loc),
485
+ loc=loc,
486
+ )
487
+ range_args = [start, end]
488
+ if bump != 1:
489
+ range_args.append(TpyIntLiteral(value=bump, loc=loc))
490
+ range_call = TpyCall(
491
+ func=TpyName(name="range", loc=loc),
492
+ args=range_args,
493
+ loc=loc,
494
+ )
495
+ body = _lower_stmt_list(
496
+ stmt.body, name_to_origin, plugin_name, fm, diags)
497
+ return TpyForEach(var=stmt.var, iterable=range_call, body=body, loc=loc)
498
+ if isinstance(stmt, ForEach):
499
+ it = _lower_expr(stmt.iter, name_to_origin, plugin_name, fm, diags)
500
+ if it is None:
501
+ return None
502
+ body = _lower_stmt_list(
503
+ stmt.body, name_to_origin, plugin_name, fm, diags)
504
+ return TpyForEach(var=stmt.var, iterable=it, body=body, loc=loc)
505
+ if isinstance(stmt, Return):
506
+ value = None
507
+ if stmt.value is not None:
508
+ value = _lower_expr(stmt.value, name_to_origin,
509
+ plugin_name, fm, diags)
510
+ if value is None:
511
+ return None
512
+ return TpyReturn(value=value, loc=loc)
513
+ if isinstance(stmt, Break):
514
+ return TpyBreak(loc=loc)
515
+ if isinstance(stmt, Continue):
516
+ return TpyContinue(loc=loc)
517
+ if isinstance(stmt, Raise):
518
+ value = None
519
+ if stmt.value is not None:
520
+ value = _lower_expr(stmt.value, name_to_origin,
521
+ plugin_name, fm, diags)
522
+ if value is None:
523
+ return None
524
+ out = TpyRaise(raise_expr=value)
525
+ out.loc = loc
526
+ return out
527
+ if isinstance(stmt, Match):
528
+ subject = _lower_expr(stmt.subject, name_to_origin, plugin_name, fm, diags)
529
+ if subject is None:
530
+ return None
531
+ tpy_cases: list[TpyMatchCase] = []
532
+ for case in stmt.cases:
533
+ pattern = _lower_pattern(case.pattern, name_to_origin,
534
+ plugin_name, fm, diags)
535
+ if pattern is None:
536
+ return None
537
+ guard = None
538
+ if case.guard is not None:
539
+ guard = _lower_expr(case.guard, name_to_origin,
540
+ plugin_name, fm, diags)
541
+ if guard is None:
542
+ return None
543
+ body = _lower_stmt_list(
544
+ case.body, name_to_origin, plugin_name, fm, diags)
545
+ tpy_cases.append(TpyMatchCase(
546
+ pattern=pattern, guard=guard, body=body,
547
+ loc=_to_source_loc(case.loc),
548
+ ))
549
+ return TpyMatch(subject=subject, cases=tpy_cases, loc=loc)
550
+ diags.append(_ir_invalid(
551
+ plugin_name, fm,
552
+ f"unsupported top-level stmt kind: {type(stmt).__name__}",
553
+ ))
554
+ return None
555
+
556
+
557
+ def _lower_stmt_list(stmts, name_to_origin, plugin_name, fm, diags):
558
+ out: list = []
559
+ for s in stmts:
560
+ lowered = _lower_stmt(s, name_to_origin, plugin_name, fm, diags)
561
+ if lowered is not None:
562
+ out.append(lowered)
563
+ return out
564
+
565
+
566
+ def _lower_enum(
567
+ en: Enum,
568
+ plugin_name: str,
569
+ fm: FrontendModule,
570
+ diags: list[FrontendDiagnostic],
571
+ ) -> TpyEnum | None:
572
+ """Lower an IR `Enum` to a `TpyEnum`.
573
+
574
+ M7 emits Pascal-style auto-numbered enums (members get values 0, 1,
575
+ 2, ...). Explicit member values from `EnumValue.value` are accepted
576
+ only when they are `IntLit`; richer expressions wait on a later
577
+ milestone since TpyEnum's members slot is `(name, int, loc)` --
578
+ pre-resolved at parser/lowering time.
579
+ """
580
+ members: list = []
581
+ auto_index = 0
582
+ for v in en.values:
583
+ if v.value is None:
584
+ int_value = auto_index
585
+ elif isinstance(v.value, IntLit):
586
+ int_value = v.value.value
587
+ else:
588
+ diags.append(_ir_invalid(
589
+ plugin_name, fm,
590
+ "EnumValue.value must be IntLit or None in M7 "
591
+ "(auto-numbering / explicit int literal)",
592
+ ))
593
+ return None
594
+ members.append((v.name, int_value, _to_source_loc(v.loc)))
595
+ auto_index = int_value + 1
596
+ return TpyEnum(
597
+ name=en.name,
598
+ members=members,
599
+ is_int_enum=False,
600
+ loc=_to_source_loc(en.loc),
601
+ )
602
+
603
+
604
+ def _is_known_record_name(name: str, fm: FrontendModule) -> bool:
605
+ """True iff `name` matches the name of a record declared in this
606
+ FrontendModule. Used during expression lowering to recognise
607
+ constructor calls (`Point()`) so the resulting TpyCall carries a
608
+ `call_type` -- the same shape the Python parser emits for
609
+ type-instantiation calls.
610
+ """
611
+ return any(rec.name == name for rec in fm.records)
612
+
613
+
614
+ def _lower_record(
615
+ rec: Record,
616
+ plugin_name: str,
617
+ fm: FrontendModule,
618
+ diags: list[FrontendDiagnostic],
619
+ qname_prefix: str,
620
+ ) -> tuple[TpyRecord | None, RecordInfo | None]:
621
+ """Lower an IR `Record` to a `TpyRecord` plus the parallel
622
+ `RecordInfo` the resolver registry consumes.
623
+
624
+ Field types lower to `TpyTypeRef`; sema's `resolve_refs` pass turns
625
+ them into real `TpyType`s using the resolver. The TpyRecord and
626
+ RecordInfo share the same FieldInfo objects (parser convention),
627
+ so mutation by either downstream consumer is visible to the other.
628
+ """
629
+ field_infos: list[FieldInfo] = []
630
+ for f in rec.fields:
631
+ t = _lower_type(f.type, plugin_name, fm, diags)
632
+ if t is None:
633
+ return None, None
634
+ default_expr = None
635
+ if f.default is not None:
636
+ default_expr = _lower_expr(
637
+ f.default, {}, plugin_name, fm, diags)
638
+ if default_expr is None:
639
+ return None, None
640
+ field_infos.append(FieldInfo(
641
+ name=f.name, type=t,
642
+ default_expr=default_expr,
643
+ loc=_to_source_loc(f.loc),
644
+ ))
645
+ if rec.nested_records or rec.nested_enums:
646
+ diags.append(_ir_invalid(
647
+ plugin_name, fm,
648
+ "nested records / enums are reserved for later milestones",
649
+ ))
650
+ return None, None
651
+ methods: list[TpyFunction] = []
652
+ for m in rec.methods:
653
+ lowered = _lower_function(m, {}, plugin_name, fm, diags)
654
+ if lowered is None:
655
+ return None, None
656
+ lowered.is_method = True
657
+ # Strip the leading `self` parameter from `params`: TPy's
658
+ # method-arg accounting (sema's `init_params`, arity checks,
659
+ # etc.) excludes self. The IR carries it because plugins
660
+ # build method bodies that reference `self` by name; for the
661
+ # TpyFunction the receiver is implicit.
662
+ if lowered.params and lowered.params[0][0] == "self":
663
+ lowered.params = lowered.params[1:]
664
+ if m.is_property_getter:
665
+ lowered.is_property_getter = True
666
+ if m.is_property_setter:
667
+ lowered.is_property_setter = True
668
+ if m.property_name is not None:
669
+ lowered.property_name = m.property_name
670
+ methods.append(lowered)
671
+ tpy_rec = TpyRecord(
672
+ name=rec.name,
673
+ fields=field_infos,
674
+ methods=methods,
675
+ )
676
+ # RecordInfo's `module` is the public module qname; entry-point
677
+ # modules use `__main__` (matches sema's `ctx.module_name` rename
678
+ # for the entry point), other modules use their dotted name.
679
+ # Builtin-type-key stays unset because plugin records aren't
680
+ # `@builtin_type` decorated.
681
+ record_info = RecordInfo(
682
+ name=rec.name,
683
+ fields=field_infos,
684
+ has_init=False,
685
+ module=qname_prefix,
686
+ )
687
+ return tpy_rec, record_info
688
+
689
+
690
+ def _lower_function(
691
+ fn: Function,
692
+ name_to_origin: dict[str, tuple[str, str]],
693
+ plugin_name: str,
694
+ fm: FrontendModule,
695
+ diags: list[FrontendDiagnostic],
696
+ ) -> TpyFunction | None:
697
+ """Lower a frontend `Function` to a `TpyFunction`. Plugins emit
698
+ functions whose params already carry resolved types (NamedType /
699
+ PointerType); the body uses the M4 expression / statement set.
700
+ """
701
+ params: list = []
702
+ for p in fn.params:
703
+ # An un-annotated param (`type is None`) is permitted for
704
+ # `self` on record methods, mirroring the no-annotation form
705
+ # in ordinary TPy source. The resolver-attached pre-pass
706
+ # treats a None-typed first-param of a method as `Self`.
707
+ if p.type is None:
708
+ t = None
709
+ else:
710
+ t = _lower_type(p.type, plugin_name, fm, diags)
711
+ if t is None:
712
+ return None
713
+ if p.default is not None:
714
+ diags.append(_ir_invalid(
715
+ plugin_name, fm,
716
+ "parameter defaults are not lowered in M4",
717
+ ))
718
+ return None
719
+ params.append((p.name, t))
720
+ return_type = None
721
+ if fn.return_type is not None:
722
+ return_type = _lower_type(fn.return_type, plugin_name, fm, diags)
723
+ if return_type is None:
724
+ return None
725
+ body = _lower_stmt_list(
726
+ fn.body, name_to_origin, plugin_name, fm, diags)
727
+ return TpyFunction(
728
+ name=fn.name,
729
+ params=params,
730
+ return_type=return_type,
731
+ body=body,
732
+ )
733
+
734
+
735
+ def _lower_pattern(p, name_to_origin, plugin_name, fm, diags):
736
+ loc = _to_source_loc(p.loc) if getattr(p, "loc", None) else None
737
+ if isinstance(p, MatchWildcard):
738
+ return TpyWildcardPattern(loc=loc)
739
+ if isinstance(p, MatchValue):
740
+ # Literal patterns (int / str / bool) route through TpyLiteralPattern.
741
+ # Named-constant patterns (`Color.Red`) keep their expression
742
+ # form via TpyValuePattern, which sema resolves to the enum
743
+ # member value at match-analysis time.
744
+ v = p.value
745
+ if isinstance(v, IntLit):
746
+ return TpyLiteralPattern(value=v.value, loc=loc)
747
+ if isinstance(v, StrLit):
748
+ return TpyLiteralPattern(value=v.value, loc=loc)
749
+ if isinstance(v, BoolLit):
750
+ return TpyLiteralPattern(value=v.value, loc=loc)
751
+ if isinstance(v, Attr):
752
+ lowered = _lower_expr(v, {}, plugin_name, fm, diags)
753
+ if lowered is None:
754
+ return None
755
+ return TpyValuePattern(expr=lowered, loc=loc)
756
+ diags.append(_ir_invalid(
757
+ plugin_name, fm,
758
+ f"MatchValue payload must be a literal or attribute "
759
+ f"reference, got {type(v).__name__}",
760
+ ))
761
+ return None
762
+ if isinstance(p, MatchClass):
763
+ cls_ref = TpyName(name=p.class_name)
764
+ cls_ref.loc = loc
765
+ class_pat = TpyClassPattern(
766
+ cls=cls_ref, positional=[], keywords=[], loc=loc,
767
+ )
768
+ if p.bind is not None:
769
+ return TpyAsPattern(pattern=class_pat, name=p.bind, loc=loc)
770
+ return class_pat
771
+ diags.append(_ir_invalid(
772
+ plugin_name, fm,
773
+ f"unsupported MatchPattern kind: {type(p).__name__}",
774
+ ))
775
+ return None
776
+
777
+
778
+ def _lower_expr(
779
+ expr,
780
+ name_to_origin: dict[str, tuple[str, str]],
781
+ plugin_name: str,
782
+ fm: FrontendModule,
783
+ diags: list[FrontendDiagnostic],
784
+ ):
785
+ loc = _to_source_loc(expr.loc) if getattr(expr, "loc", None) else None
786
+ if isinstance(expr, StrLit):
787
+ out = TpyStrLiteral(value=expr.value)
788
+ out.loc = loc
789
+ return out
790
+ if isinstance(expr, BoolLit):
791
+ out = TpyBoolLiteral(value=expr.value)
792
+ out.loc = loc
793
+ return out
794
+ if isinstance(expr, IntLit):
795
+ out = TpyIntLiteral(value=expr.value)
796
+ out.loc = loc
797
+ return out
798
+ if isinstance(expr, FloatLit):
799
+ out = TpyFloatLiteral(value=expr.value)
800
+ out.loc = loc
801
+ return out
802
+ if isinstance(expr, Name):
803
+ out = TpyName(name=expr.ident)
804
+ out.loc = loc
805
+ return out
806
+ if isinstance(expr, BinOp):
807
+ op_str = _BINOP_OP_STR.get(expr.op)
808
+ if op_str is None:
809
+ diags.append(_ir_invalid(
810
+ plugin_name, fm,
811
+ f"unsupported BinOpKind: {expr.op}",
812
+ ))
813
+ return None
814
+ lhs = _lower_expr(expr.lhs, name_to_origin, plugin_name, fm, diags)
815
+ rhs = _lower_expr(expr.rhs, name_to_origin, plugin_name, fm, diags)
816
+ if lhs is None or rhs is None:
817
+ return None
818
+ out = TpyBinOp(left=lhs, op=op_str, right=rhs)
819
+ out.loc = loc
820
+ return out
821
+ if isinstance(expr, UnaryOp):
822
+ op_str = _UNARYOP_OP_STR.get(expr.op)
823
+ if op_str is None:
824
+ diags.append(_ir_invalid(
825
+ plugin_name, fm,
826
+ f"unsupported UnaryOpKind: {expr.op}",
827
+ ))
828
+ return None
829
+ operand = _lower_expr(expr.operand, name_to_origin, plugin_name, fm, diags)
830
+ if operand is None:
831
+ return None
832
+ out = TpyUnaryOp(op=op_str, operand=operand)
833
+ out.loc = loc
834
+ return out
835
+ if isinstance(expr, SetLit):
836
+ lowered_elems: list = []
837
+ for elem in expr.elements:
838
+ le = _lower_expr(elem, name_to_origin, plugin_name, fm, diags)
839
+ if le is None:
840
+ return None
841
+ lowered_elems.append(le)
842
+ out = TpySetLiteral(elements=lowered_elems)
843
+ out.loc = loc
844
+ return out
845
+ if isinstance(expr, ListLit):
846
+ lowered_elems = []
847
+ for elem in expr.elements:
848
+ le = _lower_expr(elem, name_to_origin, plugin_name, fm, diags)
849
+ if le is None:
850
+ return None
851
+ lowered_elems.append(le)
852
+ out = TpyArrayLiteral(elements=lowered_elems)
853
+ out.loc = loc
854
+ return out
855
+ if isinstance(expr, NoneLit):
856
+ out = TpyNoneLiteral()
857
+ out.loc = loc
858
+ return out
859
+ if isinstance(expr, Compare):
860
+ if not expr.ops or len(expr.ops) != len(expr.comparators):
861
+ diags.append(_ir_invalid(
862
+ plugin_name, fm,
863
+ "Compare requires ops and comparators of equal, "
864
+ "non-zero length",
865
+ ))
866
+ return None
867
+ # Single-op comparison lowers to `TpyBinOp` (matches what the
868
+ # parser produces for a non-chained Python compare). Chained
869
+ # form (a < b < c) would lower to `TpyChainedCompare`; no
870
+ # M3-target source language uses chains, so it's left for a
871
+ # future plugin to opt into.
872
+ if len(expr.ops) != 1:
873
+ diags.append(_ir_invalid(
874
+ plugin_name, fm,
875
+ "chained comparisons are not lowered in M3",
876
+ ))
877
+ return None
878
+ op_str = _CMPOP_OP_STR.get(expr.ops[0])
879
+ if op_str is None:
880
+ diags.append(_ir_invalid(
881
+ plugin_name, fm,
882
+ f"unsupported CmpOpKind: {expr.ops[0]}",
883
+ ))
884
+ return None
885
+ lhs = _lower_expr(expr.lhs, name_to_origin, plugin_name, fm, diags)
886
+ rhs = _lower_expr(expr.comparators[0], name_to_origin,
887
+ plugin_name, fm, diags)
888
+ if lhs is None or rhs is None:
889
+ return None
890
+ out = TpyBinOp(left=lhs, op=op_str, right=rhs)
891
+ out.loc = loc
892
+ return out
893
+ if isinstance(expr, Attr):
894
+ target = _lower_expr(expr.target, name_to_origin, plugin_name, fm, diags)
895
+ if target is None:
896
+ return None
897
+ out = TpyFieldAccess(obj=target, field=expr.ident)
898
+ out.loc = loc
899
+ return out
900
+ if isinstance(expr, Subscript):
901
+ target = _lower_expr(expr.target, name_to_origin, plugin_name, fm, diags)
902
+ index = _lower_expr(expr.index, name_to_origin, plugin_name, fm, diags)
903
+ if target is None or index is None:
904
+ return None
905
+ out = TpySubscript(obj=target, index=index)
906
+ out.loc = loc
907
+ return out
908
+ if isinstance(expr, Call):
909
+ # Method-call shape: callee is `obj.method` -- emit TpyMethodCall
910
+ # directly so sema's method-dispatch path fires. TpyCall with a
911
+ # field-access function would otherwise have to be re-routed by
912
+ # sema, and not every method-resolution path handles that.
913
+ if isinstance(expr.callee, Attr):
914
+ obj = _lower_expr(expr.callee.target, name_to_origin,
915
+ plugin_name, fm, diags)
916
+ if obj is None:
917
+ return None
918
+ args: list = []
919
+ for a in expr.args:
920
+ la = _lower_expr(a, name_to_origin, plugin_name, fm, diags)
921
+ if la is None:
922
+ return None
923
+ args.append(la)
924
+ mcall = TpyMethodCall(
925
+ obj=obj, method=expr.callee.ident, args=args,
926
+ )
927
+ mcall.loc = loc
928
+ return mcall
929
+ callee = _lower_expr(expr.callee, name_to_origin, plugin_name, fm, diags)
930
+ if callee is None:
931
+ return None
932
+ args = []
933
+ for a in expr.args:
934
+ la = _lower_expr(a, name_to_origin, plugin_name, fm, diags)
935
+ if la is None:
936
+ return None
937
+ args.append(la)
938
+ kwargs: dict = {}
939
+ for kw_name, kw_value in expr.kwargs:
940
+ la = _lower_expr(kw_value, name_to_origin, plugin_name, fm, diags)
941
+ if la is None:
942
+ return None
943
+ kwargs[kw_name] = la
944
+ out = TpyCall(func=callee, args=args, kwargs=kwargs)
945
+ out.loc = loc
946
+ # `type_args` on the IR maps to the parser's `call_type`: a
947
+ # TpyTypeRef whose `args` are the explicit type/int args. This
948
+ # is what powers generic-constructor calls like
949
+ # `Array[Int32, 8]()` and `Container[T]()`.
950
+ if expr.type_args:
951
+ if not isinstance(expr.callee, Name):
952
+ diags.append(_ir_invalid(
953
+ plugin_name, fm,
954
+ "type_args only allowed on calls with a bare-name callee",
955
+ ))
956
+ return None
957
+ type_ref_args: list = []
958
+ for a in expr.type_args:
959
+ if isinstance(a, IntTypeArg):
960
+ type_ref_args.append(a.value)
961
+ elif isinstance(a, TypeTypeArg):
962
+ lowered = _lower_type(a.value, plugin_name, fm, diags)
963
+ if lowered is None:
964
+ return None
965
+ type_ref_args.append(lowered)
966
+ else:
967
+ diags.append(_ir_invalid(
968
+ plugin_name, fm,
969
+ f"unsupported TypeArg on Call: {type(a).__name__}",
970
+ ))
971
+ return None
972
+ out.call_type = TpyTypeRef(
973
+ name=expr.callee.ident, args=tuple(type_ref_args), loc=loc,
974
+ )
975
+ # If the callee is a bare Name that we imported via FromImport,
976
+ # tag the call so sema knows which module exports it (parallel
977
+ # to what Parser._resolve_call_import does for .py sources).
978
+ if isinstance(expr.callee, Name):
979
+ origin = name_to_origin.get(expr.callee.ident)
980
+ if origin is not None:
981
+ out.resolved_import = origin
982
+ # Constructor call (no explicit type_args): if the callee
983
+ # is the name of a user-declared record in this module,
984
+ # tag the call as a constructor so sema's record-instantiation
985
+ # path fires with the right module-qname.
986
+ elif (out.call_type is None
987
+ and _is_known_record_name(expr.callee.ident, fm)):
988
+ out.call_type = TpyTypeRef(
989
+ name=expr.callee.ident, args=(), loc=loc,
990
+ )
991
+ return out
992
+ diags.append(_ir_invalid(
993
+ plugin_name, fm,
994
+ f"unsupported expr kind: {type(expr).__name__}",
995
+ ))
996
+ return None
997
+
998
+
999
+ def _lower_type(
1000
+ t: TypeExpr,
1001
+ plugin_name: str,
1002
+ fm: FrontendModule,
1003
+ diags: list[FrontendDiagnostic],
1004
+ ) -> TpyTypeRef | None:
1005
+ """Lower a symbolic IR type to a parser `TpyTypeRef`.
1006
+
1007
+ M2: `NamedType` (with generic args). M4: `PointerType` for Pascal
1008
+ `var` parameters; lowers to `Ptr[T]` via the canonical resolver
1009
+ wrapper name. Other wrappers (Optional/Own/Readonly/...) land
1010
+ milestone-by-milestone.
1011
+ """
1012
+ if isinstance(t, NamedType):
1013
+ args: list = []
1014
+ for a in t.args:
1015
+ if isinstance(a, IntTypeArg):
1016
+ args.append(a.value)
1017
+ elif isinstance(a, TypeTypeArg):
1018
+ lowered = _lower_type(a.value, plugin_name, fm, diags)
1019
+ if lowered is None:
1020
+ return None
1021
+ args.append(lowered)
1022
+ else:
1023
+ diags.append(_ir_invalid(
1024
+ plugin_name, fm,
1025
+ f"unsupported TypeArg kind: {type(a).__name__}",
1026
+ ))
1027
+ return None
1028
+ return TpyTypeRef(name=t.name, args=tuple(args),
1029
+ loc=_to_source_loc(t.loc))
1030
+ if isinstance(t, PointerType):
1031
+ inner = _lower_type(t.inner, plugin_name, fm, diags)
1032
+ if inner is None:
1033
+ return None
1034
+ # `tpy:Ptr` is the canonical-name spelling TypeResolver
1035
+ # recognises for the Ptr wrapper (see type_resolver.py's
1036
+ # structural-wrapper table). The walker that parses the
1037
+ # surface form `Ptr[T]` produces this name only when the
1038
+ # source identifier truly resolved to `tpy.Ptr`; for plugin
1039
+ # lowering we know the binding by construction.
1040
+ return TpyTypeRef(name="tpy:Ptr", args=(inner,),
1041
+ loc=_to_source_loc(t.loc))
1042
+ if isinstance(t, UnionType):
1043
+ members: list = []
1044
+ for m in t.members:
1045
+ lm = _lower_type(m, plugin_name, fm, diags)
1046
+ if lm is None:
1047
+ return None
1048
+ members.append(lm)
1049
+ return TpyUnionRef(members=tuple(members),
1050
+ loc=_to_source_loc(t.loc))
1051
+ if isinstance(t, CallableType):
1052
+ params_lowered: list = []
1053
+ for p in t.params:
1054
+ lp = _lower_type(p, plugin_name, fm, diags)
1055
+ if lp is None:
1056
+ return None
1057
+ params_lowered.append(lp)
1058
+ if t.return_type is None:
1059
+ # `procedure(...)` -- the void-returning form. TPy
1060
+ # spells this as `Callable[[...], None]`; the resolver
1061
+ # accepts `NoneType` (`TpyTypeRef("None")`) as the
1062
+ # return type.
1063
+ ret = TpyTypeRef(name="None", args=(),
1064
+ loc=_to_source_loc(t.loc))
1065
+ else:
1066
+ ret = _lower_type(t.return_type, plugin_name, fm, diags)
1067
+ if ret is None:
1068
+ return None
1069
+ return TpyCallableRef(
1070
+ kind="Callable", params=tuple(params_lowered),
1071
+ return_type=ret, loc=_to_source_loc(t.loc),
1072
+ )
1073
+ diags.append(_ir_invalid(
1074
+ plugin_name, fm,
1075
+ f"unsupported TypeExpr kind: {type(t).__name__}",
1076
+ ))
1077
+ return None
1078
+
1079
+
1080
+ def _to_source_loc(loc: Loc | None) -> SourceLocation | None:
1081
+ if loc is None:
1082
+ return None
1083
+ return SourceLocation(
1084
+ line=loc.line,
1085
+ column=max(0, loc.col - 1), # parser uses 0-based columns
1086
+ file=str(loc.file),
1087
+ )
1088
+
1089
+
1090
+ def _ir_invalid(
1091
+ plugin_name: str, fm: FrontendModule, message: str,
1092
+ ) -> FrontendDiagnostic:
1093
+ return FrontendDiagnostic(
1094
+ diagnostic=Diagnostic(level=DiagnosticLevel.ERROR, message=message),
1095
+ category=FrontendDiagnosticCategory.PLUGIN_IR_INVALID,
1096
+ plugin_name=plugin_name,
1097
+ source_language=fm.source_language or None,
1098
+ )