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/macro_api.py ADDED
@@ -0,0 +1,1724 @@
1
+ """
2
+ TurboPython Macro API
3
+
4
+ Public API for macro modules. Macro modules import from this package to inspect
5
+ and modify class definitions during compilation.
6
+
7
+ Macro modules are normal .py files with a ``# tpy: macro_module`` directive.
8
+ They are executed via CPython during compilation (not compiled to C++).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import copy
14
+ import textwrap
15
+ from dataclasses import dataclass, field
16
+ from typing import Any, Callable, Literal, NoReturn, TYPE_CHECKING
17
+
18
+ from .parse.nodes import ParseError as _ParseError
19
+ from .typesys import (
20
+ TpyType, NominalType, OwnType,
21
+ OptionalType,
22
+ TupleType, UnionType, make_set, make_dict, make_list, make_span,
23
+ FieldInfo as InternalFieldInfo,
24
+ INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64,
25
+ ALL_FIXED_INTS,
26
+ is_float_type,
27
+ unwrap_final,
28
+ )
29
+ from .type_def_registry import is_str_type, is_str_view_type
30
+ from .symbol_binding import SymbolKind, lookup_imported
31
+ from .typesys import (
32
+ VOID as _VOID, STR as _STR, STRVIEW as _STRVIEW, BOOL as _BOOL,
33
+ FLOAT as _FLOAT, FLOAT32 as _FLOAT32, BIGINT as _BIGINT,
34
+ )
35
+ from .parse.parser import FragmentParser as _FragmentParser
36
+ from .parse import (
37
+ TpyRecord, TpyFunction, TpyExpr, TpyStmt,
38
+ TpyAssign, TpyFieldAccess, TpyName, TpyBinOp, TpyReturn,
39
+ TpyMethodCall, TpyCall, TpyExprStmt,
40
+ TpyIntLiteral, TpyFloatLiteral, TpyBoolLiteral, TpyStrLiteral,
41
+ TpyNoneLiteral, TpyUnaryOp,
42
+ TpyVarDecl, TpyTupleUnpack, TpyIf, TpyWhile, TpyForEach,
43
+ TpyRaise, TpyAssert, TpyExceptHandler, TpyTry,
44
+ TpyMatch, TpyMatchCase, TpyLiteralPattern, TpyWildcardPattern,
45
+ TpySubscript, TpyTupleLiteral, TpyArrayLiteral, TpyDictLiteral,
46
+ TpyListComprehension, TpyComprehensionGenerator, TpyDictComprehension,
47
+ TpySetComprehension, TpyIfExpr,
48
+ TpyFString, TpyFStringValue,
49
+ TpyPattern,
50
+ )
51
+
52
+ # Public type aliases for macro module type annotations
53
+ Expr = TpyExpr
54
+ Stmt = TpyStmt
55
+ Function = TpyFunction
56
+ Type = TpyType
57
+ MatchCase = TpyMatchCase
58
+ ExceptHandler = TpyExceptHandler
59
+ Pattern = TpyPattern
60
+ ComprehensionGenerator = TpyComprehensionGenerator
61
+
62
+ # Re-exports from the tpyc package so macro modules only need to import
63
+ # from tpyc.macro_api.
64
+ from . import __version__, VERSION_INFO
65
+
66
+ if TYPE_CHECKING:
67
+ from collections.abc import Iterator
68
+ from .sema.context import SemanticContext
69
+ from .typesys import RecordInfo
70
+
71
+
72
+ _FIXED_INT_NAMES: frozenset[str] = frozenset(str(t) for t in ALL_FIXED_INTS)
73
+
74
+
75
+ # ---------------------------------------------------------------------------
76
+ # MacroError -- raised by macros to report compile errors
77
+ # ---------------------------------------------------------------------------
78
+
79
+ class MacroError(Exception):
80
+ """Raised by macro code to report a compile error.
81
+
82
+ The macro infrastructure catches this and converts it to a SemanticError
83
+ with the appropriate source location. If ``loc`` is provided, it overrides
84
+ the default location (the decorator/call site).
85
+ """
86
+ def __init__(self, msg: str, loc: Any = None) -> None:
87
+ super().__init__(msg)
88
+ self.loc = loc
89
+
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Decorator for marking macro functions
93
+ # ---------------------------------------------------------------------------
94
+
95
+ def class_macro(fn: Callable) -> Callable:
96
+ """Mark a function as a class macro.
97
+
98
+ Class macros receive a ClassInfo and modify it (add methods, set flags).
99
+ """
100
+ fn._is_class_macro = True
101
+ return fn
102
+
103
+
104
+ def call_macro(fn: Callable) -> Callable:
105
+ """Mark a function as a call-site macro.
106
+
107
+ Call macros receive a CallMacroContext + MacroArg per argument.
108
+ They return a replacement TpyExpr that sema analyzes.
109
+ """
110
+ fn._is_call_macro = True
111
+ return fn
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Builder-trace macros (Phase 7)
116
+ # ---------------------------------------------------------------------------
117
+ # A @builder_macro class describes a state machine that the compiler walks
118
+ # while analyzing a function body. The compiler tracks the symbol bound by
119
+ # the constructor and dispatches subsequent method calls on it to the
120
+ # class's @builder_method / @builder_returns / @builder_terminal handlers.
121
+ # A terminal closes the trace by emitting synthesized declarations and
122
+ # rewriting the original call site.
123
+
124
+ def builder_macro(cls: type) -> type:
125
+ """Mark a class as a builder-trace macro.
126
+
127
+ The class's ``__init__(self, ctx: BuilderContext, args: MacroArgs)`` is
128
+ invoked when the compiler sees a constructor call to it. Methods marked
129
+ ``@builder_method`` / ``@builder_returns(child)`` / ``@builder_terminal``
130
+ are dispatched on subsequent method calls of the bound symbol.
131
+ """
132
+ cls._is_builder_macro = True
133
+ return cls
134
+
135
+
136
+ def builder_method(fn: Callable) -> Callable:
137
+ """Mark a method as a void builder step that mutates state."""
138
+ fn._is_builder_method = True
139
+ return fn
140
+
141
+
142
+ def builder_returns(child_class: type) -> Callable[[Callable], Callable]:
143
+ """Mark a method as returning a sub-builder.
144
+
145
+ The decorated method's return value (a fresh ``child_class`` instance)
146
+ becomes a new tracked symbol in the trace, allowing chained builder
147
+ calls.
148
+
149
+ The expander dispatches sub-builder method calls using the handlers
150
+ declared on the *declared* ``child_class`` -- subclass dispatch is
151
+ not supported. If a handler returns an instance of a subclass of
152
+ ``child_class``, methods declared only on the subclass will not be
153
+ found by the expander. Always declare the exact return type.
154
+
155
+ The LHS name receiving the sub-builder must not already be a
156
+ tracked builder symbol; reassignment is rejected.
157
+ """
158
+ def decorate(fn: Callable) -> Callable:
159
+ fn._is_builder_returns = True
160
+ fn._builder_child_class = child_class
161
+ return fn
162
+ return decorate
163
+
164
+
165
+ def builder_terminal(fn: Callable) -> Callable:
166
+ """Mark a method as the trace terminator.
167
+
168
+ A terminal handler:
169
+
170
+ * Emits synthesized declarations through ``ctx.emit_record`` /
171
+ ``ctx.emit_function``,
172
+ * Rewrites the original call site via ``ctx.replace_call``,
173
+ * Returns the ``TypeInfo`` for the call's result type (so the LHS in
174
+ ``x = builder.terminal(...)`` is statically typed).
175
+ """
176
+ fn._is_builder_terminal = True
177
+ return fn
178
+
179
+
180
+ # ---------------------------------------------------------------------------
181
+ # Macro module dependency declaration
182
+ # ---------------------------------------------------------------------------
183
+
184
+ # Set by macro_deps(), read by MacroRegistry.load_module().
185
+ _pending_macro_deps: dict[str, list[str] | None] | None = None
186
+
187
+
188
+ def macro_deps(*args: str | tuple[str, ...]) -> None:
189
+ """Declare modules that macro-generated code depends on.
190
+
191
+ Called at module level in a macro module. The module name is bound
192
+ in the macro namespace so macro expansions can use qualified calls
193
+ (e.g. ``ast.method_call(ast.name("mod"), "func", args)``).
194
+ Individual function names are NOT injected into user scope.
195
+
196
+ Each argument is a module name string: ``"tplib.json.parser"``.
197
+ The tuple form ``("module", "name1", ...)`` is accepted for backward
198
+ compatibility but the name filter is ignored -- use qualified calls.
199
+
200
+ Example::
201
+
202
+ macro_deps("log_infra", "tpy.unsafe")
203
+ """
204
+ global _pending_macro_deps
205
+ if _pending_macro_deps is not None:
206
+ raise MacroError("macro_deps() called twice in the same module")
207
+ deps: dict[str, list[str] | None] = {}
208
+ for arg in args:
209
+ if isinstance(arg, str):
210
+ deps[arg] = None
211
+ else:
212
+ module = arg[0]
213
+ names = list(arg[1:])
214
+ if module in deps and deps[module] is None:
215
+ pass # None (all exports) already set
216
+ elif module in deps and deps[module] is not None:
217
+ deps[module] = list(set(deps[module]) | set(names))
218
+ else:
219
+ deps[module] = names
220
+ _pending_macro_deps = deps
221
+
222
+
223
+ # ---------------------------------------------------------------------------
224
+ # MacroFStringPart -- typed expression from a decomposed f-string
225
+ # ---------------------------------------------------------------------------
226
+
227
+ @dataclass
228
+ class MacroFStringPart:
229
+ """One expression part of a decomposed f-string.
230
+
231
+ Provides the original AST expression, its resolved type, and the
232
+ format spec (if any). Used by call macros to apply per-type wrapping
233
+ when building logging or serialization calls.
234
+ """
235
+ expr: TpyExpr
236
+ type: TypeInfo
237
+ format_spec: str | None = None
238
+ conversion: int = -1 # FSTRING_CONV_NONE
239
+ is_static_str: bool = False
240
+
241
+ @property
242
+ def is_string_literal(self) -> bool:
243
+ """True if the expression is a string literal (static storage in C++)."""
244
+ return isinstance(self.expr, TpyStrLiteral)
245
+
246
+
247
+ def _is_static_str(expr: TpyExpr, ctx: 'SemanticContext | None' = None) -> bool:
248
+ """Check if an expression resolves to static string storage at compile time.
249
+
250
+ Detects:
251
+ * direct string literals,
252
+ * ternary expressions whose branches are both static strings,
253
+ * name references to module-level ``Final[str]`` constants, including
254
+ those imported transitively from other modules (``from m import X``).
255
+ """
256
+ if isinstance(expr, TpyStrLiteral):
257
+ return True
258
+ if isinstance(expr, TpyIfExpr):
259
+ return _is_static_str(expr.then_expr, ctx) and _is_static_str(expr.else_expr, ctx)
260
+ if isinstance(expr, TpyName) and ctx is not None:
261
+ return _name_is_final_str(expr.name, ctx)
262
+ return False
263
+
264
+
265
+ def _name_is_final_str(name: str, ctx: 'SemanticContext') -> bool:
266
+ """True when ``name`` refers to a module-level Final[str] constant."""
267
+ # Local module declaration -- locally-declared bindings have
268
+ # `defining_module is None`, so `lookup_imported` returns None and
269
+ # we fall through to the import path below.
270
+ if name in ctx.final_globals:
271
+ local_type = ctx.global_scope.lookup(name)
272
+ return local_type is not None and _is_str_like(local_type)
273
+ # Imported from another module
274
+ source = lookup_imported(ctx.module_attributes, name, SymbolKind.VARIABLE)
275
+ if source is None:
276
+ return False
277
+ source_module, original_name = source
278
+ module_info = ctx.registry.get_module(source_module)
279
+ if module_info is None:
280
+ return False
281
+ var_info = module_info.variables.get(original_name)
282
+ if var_info is None:
283
+ return False
284
+ return var_info.is_final and _is_str_like(var_info.type)
285
+
286
+
287
+ def _is_str_like(typ: TpyType) -> bool:
288
+ """True for `str` / `StrView` / Final wrappers thereof."""
289
+ inner = unwrap_final(typ)
290
+ return is_str_type(inner) or is_str_view_type(inner)
291
+
292
+
293
+ # ---------------------------------------------------------------------------
294
+ # MacroArg -- argument wrapper for call-site macros
295
+ # ---------------------------------------------------------------------------
296
+
297
+ @dataclass
298
+ class MacroArg:
299
+ """Argument passed to a call-site macro: the AST expression + resolved type."""
300
+ expr: TpyExpr
301
+ type: TypeInfo
302
+ _fstring_parts: list[MacroFStringPart] | None = field(default=None, repr=False)
303
+
304
+ @property
305
+ def is_fstring(self) -> bool:
306
+ """True if this argument is an f-string literal (not a plain string).
307
+
308
+ Note: as_fstring() also accepts plain string literals -- use it
309
+ directly when plain strings should be treated as static f-strings.
310
+ """
311
+ return isinstance(self.expr, TpyFString)
312
+
313
+ def as_fstring(self) -> tuple[str, list[MacroFStringPart]] | None:
314
+ """Decompose an f-string argument into format template + typed parts.
315
+
316
+ Returns (format_template, parts) where format_template has ``{}``
317
+ placeholders (with optional format specs like ``{:.2f}``) and parts
318
+ is a list of MacroFStringPart for each expression.
319
+
320
+ Also accepts plain string literals (treated as a static format
321
+ template with no expression parts).
322
+
323
+ Returns None if this argument is not an f-string or string literal.
324
+ """
325
+ if isinstance(self.expr, TpyStrLiteral):
326
+ fmt = self.expr.value.replace("{", "{{").replace("}", "}}")
327
+ return fmt, []
328
+ if not isinstance(self.expr, TpyFString):
329
+ return None
330
+ assert self._fstring_parts is not None, \
331
+ "f-string parts not pre-computed (internal error)"
332
+ fmt_pieces: list[str] = []
333
+ for part in self.expr.parts:
334
+ if isinstance(part, str):
335
+ fmt_pieces.append(part.replace("{", "{{").replace("}", "}}"))
336
+ elif isinstance(part, TpyFStringValue):
337
+ spec = part.format_spec
338
+ if spec is not None:
339
+ fmt_pieces.append("{:" + spec + "}")
340
+ else:
341
+ fmt_pieces.append("{}")
342
+ return "".join(fmt_pieces), list(self._fstring_parts)
343
+
344
+
345
+ # ---------------------------------------------------------------------------
346
+ # CallMacroContext -- context for call-site macros
347
+ # ---------------------------------------------------------------------------
348
+
349
+ class CallMacroContext:
350
+ """Context passed to call-site macros for type introspection and diagnostics."""
351
+
352
+ def __init__(self, ctx: SemanticContext, loc: Any = None) -> None:
353
+ self._ctx = ctx
354
+ self._loc = loc
355
+
356
+ def get_record_fields(self, name: str) -> list[FieldInfo] | None:
357
+ """Get all fields (parent + own) for a macro record, or None.
358
+
359
+ Only returns fields for records that called set_match_args().
360
+ """
361
+ record_info = self._ctx.registry.get_record(name)
362
+ if record_info is None or record_info.match_args is None:
363
+ return None
364
+ all_fields = self._ctx.registry.get_all_fields(record_info)
365
+ return [FieldInfo.from_internal(f) for f in all_fields]
366
+
367
+ def get_iterable_element_type(self, type_info: TypeInfo) -> TypeInfo | None:
368
+ """Get the element type of an iterable type, or None if not iterable.
369
+
370
+ Handles built-in containers, user types with __iter__, and protocols.
371
+ """
372
+ if type_info._tpy_type is None:
373
+ return None
374
+ from .sema.list_literals import IterableHelper
375
+ helper = IterableHelper(self._ctx)
376
+ elem = helper.get_iterable_element_type_or_none(type_info._tpy_type)
377
+ if elem is not None:
378
+ return TypeInfo.from_tpy_type(elem)
379
+ return None
380
+
381
+ @property
382
+ def in_method(self) -> bool:
383
+ """True if the call site is inside a method body (self is available)."""
384
+ record = self._ctx.record_ctx.record
385
+ if record is None:
386
+ return False
387
+ func = self._ctx.func.current_function
388
+ return isinstance(func, TpyFunction) and func.is_method
389
+
390
+ @property
391
+ def self_type(self) -> TypeInfo | None:
392
+ """The current class as TypeInfo, or None if not in a method."""
393
+ record = self._ctx.record_ctx.record
394
+ if record is None:
395
+ return None
396
+ func = self._ctx.func.current_function
397
+ if not isinstance(func, TpyFunction) or not func.is_method:
398
+ return None
399
+ return TypeInfo.from_tpy_type(NominalType(record.name))
400
+
401
+ @property
402
+ def first_param(self) -> tuple[str, TypeInfo] | None:
403
+ """First parameter of the current function (self for methods), or None.
404
+
405
+ For methods, returns ("self", <class TypeInfo>).
406
+ For free functions, returns the first declared parameter.
407
+ """
408
+ func = self._ctx.func.current_function
409
+ if not isinstance(func, TpyFunction):
410
+ return None
411
+ if func.is_method and not func.is_staticmethod:
412
+ st = self.self_type
413
+ if st is not None:
414
+ return ("self", st)
415
+ return None
416
+ if not func.params:
417
+ return None
418
+ name, tpy_type = func.params[0]
419
+ return (name, TypeInfo.from_tpy_type(tpy_type))
420
+
421
+ def self_field(self, name: str) -> TpyExpr:
422
+ """Return AST for ``self.<name>``. Only valid when in_method is True."""
423
+ if not self.in_method:
424
+ self.error("self_field() called outside a method context")
425
+ return TpyFieldAccess(obj=TpyName("self"), field=name)
426
+
427
+ def _get_record_info(self, type_info: TypeInfo) -> Any:
428
+ """Look up RecordInfo for a TypeInfo, or None."""
429
+ if type_info._tpy_type is None:
430
+ return None
431
+ return self._ctx.registry.get_record(type_info.name)
432
+
433
+ def get_field_type(self, type_info: TypeInfo, name: str) -> TypeInfo | None:
434
+ """Get the type of a named field on a record (including inherited), or None."""
435
+ record_info = self._get_record_info(type_info)
436
+ if record_info is None:
437
+ return None
438
+ for f in self._ctx.registry.get_all_fields(record_info):
439
+ if f.name == name:
440
+ return TypeInfo.from_tpy_type(f.type)
441
+ return None
442
+
443
+ def get_method_return_type(self, type_info: TypeInfo, name: str) -> TypeInfo | None:
444
+ """Get the return type of a named method on a record (including inherited), or None."""
445
+ record_info = self._get_record_info(type_info)
446
+ if record_info is None:
447
+ return None
448
+ overloads = self._ctx.registry.get_method_overloads_with_parents(record_info, name)
449
+ if not overloads:
450
+ return None
451
+ return TypeInfo.from_tpy_type(overloads[0].return_type)
452
+
453
+ def qualified_name(self, type_info: TypeInfo) -> str:
454
+ """Module-qualified type name (e.g. 'log_infra.LogHandle').
455
+
456
+ For builtin/module types, uses the type's own qualified name.
457
+ For user records, searches the registry modules.
458
+ Falls back to the short name if the module is unknown.
459
+ """
460
+ tpy_type = type_info._tpy_type
461
+ if isinstance(tpy_type, NominalType):
462
+ qn = tpy_type.qualified_name()
463
+ if qn is not None:
464
+ return qn
465
+ # Search registry modules for user-defined records
466
+ for mod_name, mod_info in self._ctx.registry.modules.items():
467
+ if type_info.name in mod_info.records:
468
+ return f"{mod_name}.{type_info.name}"
469
+ return type_info.name
470
+
471
+ def warning(self, msg: str, loc: Any = None) -> None:
472
+ """Emit a compiler warning."""
473
+ self._ctx.warning_from_loc(msg, loc or self._loc)
474
+
475
+ def error(self, msg: str, loc: Any = None) -> NoReturn:
476
+ """Raise a compile error."""
477
+ from .diagnostics import SemanticError
478
+ raise SemanticError(msg, loc or self._loc)
479
+
480
+
481
+ # ---------------------------------------------------------------------------
482
+ # MacroArgs -- positional + keyword args delivered to builder handlers
483
+ # ---------------------------------------------------------------------------
484
+
485
+ @dataclass
486
+ class MacroArgs:
487
+ """Positional and keyword arguments passed to a builder handler.
488
+
489
+ Each value is a ``MacroArg`` (raw expression + resolved type).
490
+ Use the ``BuilderContext`` extractors (``positional_str``,
491
+ ``kwarg_int``, ...) to pull macro-time values out of these.
492
+ """
493
+ positional: list[MacroArg] = field(default_factory=list)
494
+ kwargs: dict[str, MacroArg] = field(default_factory=dict)
495
+
496
+
497
+ # ---------------------------------------------------------------------------
498
+ # BuilderContext -- API surface for @builder_macro handlers
499
+ # ---------------------------------------------------------------------------
500
+
501
+ # Sentinel: distinguishes "kwarg absent, use default" from "kwarg
502
+ # explicitly set to None". Macro authors never see this -- it stays
503
+ # inside the BuilderContext extractors.
504
+ _UNSET = object()
505
+
506
+
507
+ class BuilderContext:
508
+ """Context passed to @builder_macro handlers.
509
+
510
+ Provides:
511
+
512
+ * **Diagnostics**: ``warning(msg, loc=None)`` / ``error(msg, loc=None)``.
513
+ * **Macro-time literal evaluators**: ``eval_literal_or_final(expr)`` and
514
+ ``eval_sequence_of_literal_or_final(expr)`` -- the primitives over
515
+ which all kwarg extractors are built.
516
+ * **Typed extractors**: ``positional_str(args, i)``, ``kwarg_str``,
517
+ ``kwarg_int``, ``kwarg_bool``, ``kwarg_str_or_int``, ``kwarg_type``,
518
+ ``kwarg_list_literal``, ``kwarg_macroarg``.
519
+ * **Code emission**: ``fresh_module_name(hint)``, ``emit_record``,
520
+ ``emit_function``, ``replace_call``.
521
+
522
+ Construction is internal -- the BuilderTraceExpander wires up
523
+ ``_expander``, ``_ctx``, ``_call_loc``, and ``_function_being_traced``.
524
+
525
+ The code-emission methods are intentionally macro-kind-agnostic --
526
+ they only depend on the module, the registrar, and the per-module
527
+ fresh-name counter on SemanticContext. When a class-macro or
528
+ call-macro consumer needs to emit sibling records or top-level
529
+ helper functions, lift these methods into a shared ``ModuleEmitter``
530
+ helper that ``ClassInfo`` and ``CallMacroContext`` can hold a
531
+ reference to. Tracked as "Top-level companion records / functions"
532
+ in docs/MACRO_DESIGN.md's Macro System Future Work table.
533
+ """
534
+
535
+ def __init__(
536
+ self, expander: Any, ctx: 'SemanticContext',
537
+ call_loc: Any, function_being_traced: str,
538
+ ) -> None:
539
+ self._expander = expander
540
+ self._ctx = ctx
541
+ self._call_loc = call_loc
542
+ self._function_being_traced = function_being_traced
543
+
544
+ # -- Diagnostics --
545
+
546
+ @property
547
+ def call_loc(self) -> Any:
548
+ """Source location of the call currently being expanded."""
549
+ return self._call_loc
550
+
551
+ @property
552
+ def function_being_traced(self) -> str:
553
+ """Name of the enclosing function whose body is being traced."""
554
+ return self._function_being_traced
555
+
556
+ def warning(self, msg: str, loc: Any = None) -> None:
557
+ """Emit a compiler warning at the current call site (or `loc`)."""
558
+ self._ctx.warning_from_loc(msg, loc or self._call_loc)
559
+
560
+ def error(self, msg: str, loc: Any = None) -> NoReturn:
561
+ """Raise a compile error at the current call site (or `loc`)."""
562
+ from .diagnostics import SemanticError
563
+ raise SemanticError(msg, loc or self._call_loc)
564
+
565
+ # -- Macro-time literal evaluators (the primitives) --
566
+
567
+ def eval_literal_or_final(self, expr: TpyExpr) -> object:
568
+ """Evaluate an expression to a Python value at macro time.
569
+
570
+ Accepts int/str/float/bool/None literals, unary minus on
571
+ numeric literals, and tuple/list literals whose elements are
572
+ themselves ``eval_literal_or_final``-compatible (returns a
573
+ Python ``list`` in that case -- nested lists work the same
574
+ way). The name is forward-looking: ``Final[...]`` constant
575
+ references will resolve here once the lookup-by-name path is
576
+ wired (today the resolver returns _UNSET for any name, so
577
+ only literals work). Raises ``MacroError`` if the expression
578
+ is not statically knowable.
579
+ """
580
+ return _eval_literal_or_final(expr, self._ctx, self._call_loc)
581
+
582
+ def eval_sequence_of_literal_or_final(self, expr: TpyExpr) -> list[object]:
583
+ """Evaluate a tuple/list literal whose elements are all
584
+ ``eval_literal_or_final``-compatible.
585
+ """
586
+ if isinstance(expr, (TpyTupleLiteral, TpyArrayLiteral)):
587
+ return [self.eval_literal_or_final(e) for e in expr.elements]
588
+ raise MacroError(
589
+ f"expected a tuple or list literal, got {type(expr).__name__}",
590
+ loc=self._call_loc,
591
+ )
592
+
593
+ # -- Typed positional extractors --
594
+
595
+ def positional_strs(self, args: MacroArgs) -> list[str]:
596
+ """Extract every positional arg as a string literal value.
597
+
598
+ Errors if any positional is not a string literal.
599
+ """
600
+ out: list[str] = []
601
+ for i, ma in enumerate(args.positional):
602
+ v = self.eval_literal_or_final(ma.expr)
603
+ if not isinstance(v, str):
604
+ raise MacroError(
605
+ f"positional argument #{i} must be a string, got {type(v).__name__}",
606
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
607
+ )
608
+ out.append(v)
609
+ return out
610
+
611
+ def positional_str(self, args: MacroArgs, i: int) -> str:
612
+ if i >= len(args.positional):
613
+ raise MacroError(
614
+ f"missing positional argument #{i}", loc=self._call_loc)
615
+ ma = args.positional[i]
616
+ v = self.eval_literal_or_final(ma.expr)
617
+ if not isinstance(v, str):
618
+ raise MacroError(
619
+ f"positional argument #{i} must be a string, got {type(v).__name__}",
620
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
621
+ )
622
+ return v
623
+
624
+ # -- Typed kwarg extractors --
625
+
626
+ def _get_kwarg(self, args: MacroArgs, name: str) -> MacroArg | None:
627
+ return args.kwargs.get(name)
628
+
629
+ def kwarg_str(
630
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
631
+ ) -> str | None:
632
+ ma = self._get_kwarg(args, name)
633
+ if ma is None:
634
+ return None if default is _UNSET else default
635
+ v = self.eval_literal_or_final(ma.expr)
636
+ if v is None:
637
+ return None
638
+ if not isinstance(v, str):
639
+ raise MacroError(
640
+ f"keyword argument '{name}' must be a string, got {type(v).__name__}",
641
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
642
+ )
643
+ return v
644
+
645
+ def kwarg_int(
646
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
647
+ ) -> int | None:
648
+ ma = self._get_kwarg(args, name)
649
+ if ma is None:
650
+ return None if default is _UNSET else default
651
+ v = self.eval_literal_or_final(ma.expr)
652
+ if v is None:
653
+ return None
654
+ if not isinstance(v, int) or isinstance(v, bool):
655
+ raise MacroError(
656
+ f"keyword argument '{name}' must be an int, got {type(v).__name__}",
657
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
658
+ )
659
+ return v
660
+
661
+ def kwarg_bool(
662
+ self, args: MacroArgs, name: str, default: bool = False,
663
+ ) -> bool:
664
+ ma = self._get_kwarg(args, name)
665
+ if ma is None:
666
+ return default
667
+ v = self.eval_literal_or_final(ma.expr)
668
+ if not isinstance(v, bool):
669
+ raise MacroError(
670
+ f"keyword argument '{name}' must be a bool, got {type(v).__name__}",
671
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
672
+ )
673
+ return v
674
+
675
+ def kwarg_str_or_int(
676
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
677
+ ) -> Any:
678
+ ma = self._get_kwarg(args, name)
679
+ if ma is None:
680
+ return None if default is _UNSET else default
681
+ v = self.eval_literal_or_final(ma.expr)
682
+ if v is None:
683
+ return None
684
+ if isinstance(v, bool) or not isinstance(v, (str, int)):
685
+ raise MacroError(
686
+ f"keyword argument '{name}' must be a string or int, got {type(v).__name__}",
687
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
688
+ )
689
+ return v
690
+
691
+ def kwarg_type(
692
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
693
+ ) -> TypeInfo | None:
694
+ """Extract a kwarg whose value is a type reference (e.g. ``type=int``)."""
695
+ ma = self._get_kwarg(args, name)
696
+ if ma is None:
697
+ return None if default is _UNSET else default
698
+ return ma.type
699
+
700
+ def kwarg_list_literal(
701
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
702
+ ) -> list[MacroArg] | None:
703
+ """Extract a kwarg that must be a tuple or list literal.
704
+
705
+ Returns the element ``MacroArg``s wrapped in MacroArg form so the
706
+ caller can inspect each element's type and expression.
707
+ """
708
+ ma = self._get_kwarg(args, name)
709
+ if ma is None:
710
+ return None if default is _UNSET else default
711
+ if not isinstance(ma.expr, (TpyTupleLiteral, TpyArrayLiteral)):
712
+ raise MacroError(
713
+ f"keyword argument '{name}' must be a tuple or list literal",
714
+ loc=getattr(ma.expr, "loc", None) or self._call_loc,
715
+ )
716
+ # Wrap each element with an unknown type -- callers that need
717
+ # concrete element types should use eval_sequence_of_literal_or_final
718
+ # or apply their own per-element extractors.
719
+ return [MacroArg(expr=e, type=TypeInfo("?", _tpy_type=None))
720
+ for e in ma.expr.elements]
721
+
722
+ def kwarg_macroarg(
723
+ self, args: MacroArgs, name: str, default: Any = _UNSET,
724
+ ) -> MacroArg | None:
725
+ """Return the raw MacroArg for a kwarg, or ``default`` if absent."""
726
+ ma = self._get_kwarg(args, name)
727
+ if ma is None:
728
+ return None if default is _UNSET else default
729
+ return ma
730
+
731
+ # -- Type introspection --
732
+
733
+ def get_static_method_return_type(
734
+ self, type_info: TypeInfo, name: str,
735
+ ) -> "TypeInfo | None":
736
+ """Return type of a ``@staticmethod`` named ``name`` on ``type_info``,
737
+ or ``None`` when the method is absent or non-static.
738
+
739
+ One call answers "is this type usable as a structurally-typed
740
+ factory" -- macros use it to detect protocol-like hooks (e.g.
741
+ argparse's ``ArgType`` ``from_arg`` factory) without needing
742
+ the type to inherit from a formal Protocol.
743
+
744
+ Looked up by name -- builder-trace passes user-defined types
745
+ with ``_tpy_type=None`` (full type-name resolution in builder
746
+ contexts is still pending), so ``type_info.name`` is the only
747
+ reliable handle.
748
+ """
749
+ record_info = self._ctx.registry.get_record(type_info.name)
750
+ if record_info is None:
751
+ return None
752
+ overloads = self._ctx.registry.get_method_overloads_with_parents(
753
+ record_info, name)
754
+ if not overloads or not overloads[0].is_staticmethod:
755
+ return None
756
+ return TypeInfo.from_tpy_type(overloads[0].return_type)
757
+
758
+ def resolve_type_info(self, type_info: TypeInfo) -> TypeInfo:
759
+ """Materialize a builder-trace user-type placeholder into a
760
+ qname-bearing ``TypeInfo``, or return the input unchanged.
761
+
762
+ Builder-trace receives user-defined ``type=`` references with
763
+ ``_tpy_type=None`` (full type-name resolution at the builder
764
+ layer is still pending). Macros that emit field types or
765
+ record fields backed by such a placeholder need the resolved,
766
+ qname-bearing ``NominalType`` so identity matches between the
767
+ emitted-record's field and sema-resolved references in the
768
+ synthesized function body. Built-in types that already carry a
769
+ ``_tpy_type`` are returned unchanged.
770
+ """
771
+ if type_info._tpy_type is not None:
772
+ return type_info
773
+ record_info = self._ctx.registry.get_record(type_info.name)
774
+ if record_info is None:
775
+ return type_info
776
+ qname = record_info.qualified_name()
777
+ if not qname:
778
+ return type_info
779
+ return TypeInfo.from_tpy_type(
780
+ NominalType(type_info.name, _module_qname=qname)
781
+ )
782
+
783
+ # -- Code emission --
784
+
785
+ def fresh_module_name(self, hint: str) -> str:
786
+ """Synthesize a unique module-private name for a generated record
787
+ or function. Names start with an underscore so they aren't exported.
788
+ """
789
+ return self._expander.fresh_module_name(hint)
790
+
791
+ def emit_record(
792
+ self, name: str, fields: list[tuple[str, TpyType]],
793
+ methods: list[Function] | None = None,
794
+ ) -> TypeInfo:
795
+ """Synthesize and register a record type.
796
+
797
+ ``fields`` is a list of ``(name, type)`` pairs. Returns the
798
+ ``TypeInfo`` for the new type so handlers can use it as the return
799
+ type of an emitted function.
800
+ """
801
+ return self._expander.emit_record(name, fields, methods or [],
802
+ loc=self._call_loc)
803
+
804
+ def emit_function(
805
+ self, name: str, params: list[tuple[str, TpyType]],
806
+ return_type: TpyType, body: list[Stmt],
807
+ ) -> str:
808
+ """Synthesize and register a free function. Returns its module-
809
+ private name (which is also what ``replace_call`` expects).
810
+ """
811
+ return self._expander.emit_function(name, params, return_type, body,
812
+ loc=self._call_loc)
813
+
814
+ def replace_call(self, fn_name: str, args: MacroArgs) -> None:
815
+ """Rewrite the trace's terminal call site to ``fn_name(<args>)``.
816
+
817
+ Positional and keyword args from ``args`` are passed through
818
+ unchanged.
819
+ """
820
+ self._expander.replace_call(fn_name, args, loc=self._call_loc)
821
+
822
+
823
+ def _eval_literal_or_final(
824
+ expr: TpyExpr, ctx: 'SemanticContext', loc: Any,
825
+ ) -> object:
826
+ """Implementation of BuilderContext.eval_literal_or_final."""
827
+ if isinstance(expr, TpyBoolLiteral):
828
+ return expr.value
829
+ if isinstance(expr, TpyIntLiteral):
830
+ return expr.value
831
+ if isinstance(expr, TpyFloatLiteral):
832
+ return expr.value
833
+ if isinstance(expr, TpyStrLiteral):
834
+ return expr.value
835
+ if isinstance(expr, TpyNoneLiteral):
836
+ return None
837
+ if isinstance(expr, TpyUnaryOp) and expr.op == "-":
838
+ inner = _eval_literal_or_final(expr.operand, ctx, loc)
839
+ if isinstance(inner, bool) or not isinstance(inner, (int, float)):
840
+ raise MacroError(
841
+ f"unary minus requires a numeric literal, got {type(inner).__name__}",
842
+ loc=getattr(expr, "loc", None) or loc,
843
+ )
844
+ return -inner
845
+ if isinstance(expr, (TpyTupleLiteral, TpyArrayLiteral)):
846
+ return [_eval_literal_or_final(e, ctx, loc) for e in expr.elements]
847
+ if isinstance(expr, TpyName):
848
+ # Final[...] global with a literal initializer -- look it up.
849
+ v = _resolve_final_literal(expr.name, ctx)
850
+ if v is not _UNSET:
851
+ return v
852
+ raise MacroError(
853
+ f"expected a literal or Final constant, got {type(expr).__name__}",
854
+ loc=getattr(expr, "loc", None) or loc,
855
+ )
856
+
857
+
858
+ def _resolve_final_literal(name: str, ctx: 'SemanticContext') -> object:
859
+ """Resolve a name to the Python value of a Final[...] literal global,
860
+ or return _UNSET if not resolvable.
861
+
862
+ Final-literal evaluation requires walking the module's top-level
863
+ decls to find the initializer expression. v1 doesn't yet thread
864
+ that through to BuilderContext, so this always returns _UNSET --
865
+ builder kwargs must currently be literal expressions, not Final
866
+ references. Add init-expr lookup here when the use case appears.
867
+ """
868
+ return _UNSET
869
+
870
+
871
+ # ---------------------------------------------------------------------------
872
+ # TypeInfo -- read-only wrapper around TpyType
873
+ # ---------------------------------------------------------------------------
874
+
875
+ @dataclass
876
+ class TypeInfo:
877
+ """Read-only type metadata exposed to macros."""
878
+ name: str
879
+ type_args: list[TypeInfo] = field(default_factory=list)
880
+ is_optional: bool = False
881
+ is_value_type: bool = True
882
+ is_record: bool = False
883
+ _tpy_type: TpyType | None = None
884
+
885
+
886
+ @property
887
+ def raw_type(self) -> TpyType:
888
+ """Access the underlying compiler type object (escape hatch)."""
889
+ assert self._tpy_type is not None, "raw_type on TypeInfo with no underlying type"
890
+ return self._tpy_type
891
+
892
+ @property
893
+ def is_str(self) -> bool:
894
+ """True if this is any string type (str, String, StrView)."""
895
+ from .typesys import is_any_str_type
896
+ return self._tpy_type is not None and is_any_str_type(self._tpy_type)
897
+
898
+ @property
899
+ def is_int(self) -> bool:
900
+ from .type_def_registry import is_fixed_int_type
901
+ return is_fixed_int_type(self._tpy_type)
902
+
903
+ @property
904
+ def is_int32(self) -> bool:
905
+ from .type_def_registry import int_traits_of
906
+ tr = int_traits_of(self._tpy_type)
907
+ return tr is not None and tr.bits == 32 and tr.signed
908
+
909
+ @property
910
+ def is_float(self) -> bool:
911
+ return is_float_type(self._tpy_type)
912
+
913
+ @property
914
+ def is_float32(self) -> bool:
915
+ from .type_def_registry import is_float32_type
916
+ return is_float32_type(self._tpy_type)
917
+
918
+ @property
919
+ def is_bool(self) -> bool:
920
+ from .type_def_registry import is_bool_type
921
+ return is_bool_type(self._tpy_type)
922
+
923
+ @property
924
+ def is_bigint(self) -> bool:
925
+ from .type_def_registry import is_big_int_type
926
+ return is_big_int_type(self._tpy_type)
927
+
928
+ @property
929
+ def is_enum(self) -> bool:
930
+ from .type_def_registry import is_enum_type
931
+ return is_enum_type(self._tpy_type)
932
+
933
+ @property
934
+ def is_list(self) -> bool:
935
+ from .type_def_registry import is_list as _is_list
936
+ return _is_list(self._tpy_type)
937
+
938
+ @property
939
+ def is_dict(self) -> bool:
940
+ from .type_def_registry import is_dict as _is_dict
941
+ return _is_dict(self._tpy_type)
942
+
943
+ @property
944
+ def is_set(self) -> bool:
945
+ from .type_def_registry import is_set as _is_set
946
+ return _is_set(self._tpy_type)
947
+
948
+ @property
949
+ def is_tuple(self) -> bool:
950
+ return isinstance(self._tpy_type, TupleType)
951
+
952
+ @property
953
+ def enum_name(self) -> str:
954
+ """Get the enum class name (only valid when is_enum is True)."""
955
+ from .type_def_registry import is_enum_type
956
+ assert is_enum_type(self._tpy_type)
957
+ return self._tpy_type.name
958
+
959
+ @property
960
+ def int_type_name(self) -> str:
961
+ """Get the fixed-int type name e.g. 'Int32' (only valid when is_int)."""
962
+ from .type_def_registry import is_fixed_int_type
963
+ assert is_fixed_int_type(self._tpy_type)
964
+ return str(self._tpy_type)
965
+
966
+ def unwrap_optional(self) -> TypeInfo | None:
967
+ """If this is Optional[T], return TypeInfo for T. Otherwise None."""
968
+ if self.is_optional and isinstance(self._tpy_type, OptionalType):
969
+ return TypeInfo.from_tpy_type(self._tpy_type.inner)
970
+ return None
971
+
972
+ @property
973
+ def tuple_element_types(self) -> list[TypeInfo]:
974
+ """Get element types of a tuple (only valid when is_tuple)."""
975
+ assert isinstance(self._tpy_type, TupleType)
976
+ return [TypeInfo.from_tpy_type(et) for et in self._tpy_type.element_types]
977
+
978
+ @staticmethod
979
+ def from_tpy_type(typ: TpyType) -> TypeInfo:
980
+ from .typesys import RefType
981
+ # Strip sema-internal wrappers -- macros see user-facing types
982
+ if isinstance(typ, OwnType):
983
+ typ = typ.wrapped
984
+ if isinstance(typ, RefType):
985
+ typ = typ.wrapped
986
+ type_args: list[TypeInfo] = []
987
+ if isinstance(typ, TupleType):
988
+ type_args = [TypeInfo.from_tpy_type(et) for et in typ.element_types]
989
+ elif isinstance(typ, NominalType) and typ.type_args:
990
+ type_args = [TypeInfo.from_tpy_type(ta) for ta in typ.type_args]
991
+ return TypeInfo(
992
+ name=str(typ),
993
+ type_args=type_args,
994
+ is_optional=isinstance(typ, OptionalType),
995
+ is_value_type=typ.is_value_type(),
996
+ is_record=isinstance(typ, NominalType) and typ.is_user_record,
997
+ _tpy_type=typ,
998
+ )
999
+
1000
+
1001
+ # ---------------------------------------------------------------------------
1002
+ # FieldInfo -- read-only wrapper around typesys.FieldInfo
1003
+ # ---------------------------------------------------------------------------
1004
+
1005
+ @dataclass
1006
+ class FieldInfo:
1007
+ """Field metadata exposed to macros."""
1008
+ name: str
1009
+ type: TypeInfo
1010
+ has_default: bool
1011
+ default_expr: TpyExpr | None = None
1012
+ is_factory_default: bool = False
1013
+ loc: Any = None
1014
+ default_obj: Any = None
1015
+ _internal: InternalFieldInfo | None = None
1016
+
1017
+ @staticmethod
1018
+ def from_internal(fld: InternalFieldInfo) -> FieldInfo:
1019
+ return FieldInfo(
1020
+ name=fld.name,
1021
+ type=TypeInfo.from_tpy_type(fld.type),
1022
+ has_default=fld.default_expr is not None,
1023
+ default_expr=fld.default_expr,
1024
+ is_factory_default=fld.is_factory_default,
1025
+ loc=fld.loc,
1026
+ _internal=fld,
1027
+ )
1028
+
1029
+ def set_default(self, expr: TpyExpr | int | float | str | bool | None,
1030
+ default_value: str | None = None,
1031
+ is_factory: bool = False) -> None:
1032
+ """Replace this field's default expression (updates the underlying record).
1033
+
1034
+ Accepts plain Python values (from unwrapped macro kwargs) and wraps
1035
+ them in TpyExpr nodes automatically.
1036
+ """
1037
+ expr = _wrap_literal(expr)
1038
+ self.default_expr = expr
1039
+ self.has_default = expr is not None
1040
+ self.is_factory_default = is_factory
1041
+ if self._internal is not None:
1042
+ self._internal.default_expr = expr
1043
+ self._internal.default_value = default_value
1044
+ self._internal.is_factory_default = is_factory
1045
+
1046
+ def clear_default(self) -> None:
1047
+ """Remove the field's default (e.g. after unwrapping a descriptor)."""
1048
+ self.default_expr = None
1049
+ self.has_default = False
1050
+ self.is_factory_default = False
1051
+ if self._internal is not None:
1052
+ self._internal.default_expr = None
1053
+ self._internal.default_value = None
1054
+ self._internal.is_factory_default = False
1055
+
1056
+ def to_internal(self) -> InternalFieldInfo:
1057
+ """Return the underlying compiler FieldInfo."""
1058
+ if self._internal is not None:
1059
+ return self._internal
1060
+ assert self.type._tpy_type is not None
1061
+ return InternalFieldInfo(
1062
+ name=self.name,
1063
+ type=self.type._tpy_type,
1064
+ default_expr=self.default_expr,
1065
+ is_factory_default=self.is_factory_default,
1066
+ )
1067
+
1068
+
1069
+ # ---------------------------------------------------------------------------
1070
+ # MethodInfo -- read-only view of a method
1071
+ # ---------------------------------------------------------------------------
1072
+
1073
+ @dataclass
1074
+ class MethodInfo:
1075
+ """Method metadata exposed to macros (read-only)."""
1076
+ name: str
1077
+ is_readonly: bool = False
1078
+ is_staticmethod: bool = False
1079
+
1080
+
1081
+ # ---------------------------------------------------------------------------
1082
+ # ClassInfo -- mutable wrapper for macro class transformation
1083
+ # ---------------------------------------------------------------------------
1084
+
1085
+ class ClassInfo:
1086
+ """Class metadata passed to class macros.
1087
+
1088
+ Macros inspect fields/methods and can add methods, set flags, and
1089
+ set match_args. Mutations are applied back to the TpyRecord
1090
+ via ``apply_to_record()``.
1091
+ """
1092
+
1093
+ def __init__(
1094
+ self,
1095
+ record: TpyRecord,
1096
+ ctx: SemanticContext,
1097
+ macro_origin: str | None = None,
1098
+ ) -> None:
1099
+ self._record = record
1100
+ self._ctx = ctx
1101
+ self._added_methods: list[TpyFunction] = []
1102
+ self._match_args: list[str] | None = None
1103
+ # Name of the macro currently expanding through this ClassInfo
1104
+ # (e.g. "dataclass", "model"). Stamped on every TpyFunction passed
1105
+ # to `add_method` so diagnostics can attribute the synthesis to a
1106
+ # specific decorator without each call site repeating the name.
1107
+ self._macro_origin: str | None = macro_origin
1108
+
1109
+ # Populate read-only views
1110
+ self.name: str = record.name
1111
+ self.type_params: list[str] = list(record.type_params)
1112
+ self.fields: list[FieldInfo] = [
1113
+ FieldInfo.from_internal(f) for f in record.fields
1114
+ ]
1115
+ self.parent: TypeInfo | None = (
1116
+ TypeInfo.from_tpy_type(record.bases[0])
1117
+ if record.bases else None
1118
+ )
1119
+
1120
+ # -- Flags (settable by macro, propagated to TpyRecord) --
1121
+
1122
+ @property
1123
+ def is_frozen(self) -> bool:
1124
+ return self._record.is_frozen
1125
+
1126
+ @is_frozen.setter
1127
+ def is_frozen(self, value: bool) -> None:
1128
+ self._record.is_frozen = value
1129
+
1130
+ # -- Read methods --
1131
+
1132
+ @property
1133
+ def methods(self) -> list[MethodInfo]:
1134
+ """Current methods on the class (including macro-added ones)."""
1135
+ result = [
1136
+ MethodInfo(
1137
+ name=m.name,
1138
+ is_readonly=m.is_readonly,
1139
+ is_staticmethod=m.is_staticmethod,
1140
+ )
1141
+ for m in self._record.methods
1142
+ ]
1143
+ result.extend(
1144
+ MethodInfo(
1145
+ name=m.name,
1146
+ is_readonly=m.is_readonly,
1147
+ is_staticmethod=m.is_staticmethod,
1148
+ )
1149
+ for m in self._added_methods
1150
+ )
1151
+ return result
1152
+
1153
+ def has_method(self, name: str) -> bool:
1154
+ """Check if the class declares (or has had a macro add) a
1155
+ method with the given name. Self-only; inherited methods do
1156
+ not count -- mirrors CPython's `name in cls.__dict__` check
1157
+ that decorators like `@dataclass` use to decide whether to
1158
+ synthesize a method (overriding a parent's is intentional).
1159
+ Macros that want CPython's MRO semantics use
1160
+ `has_method_or_inherited` instead.
1161
+ """
1162
+ for m in self._record.methods:
1163
+ if m.name == name:
1164
+ return True
1165
+ for m in self._added_methods:
1166
+ if m.name == name:
1167
+ return True
1168
+ return False
1169
+
1170
+ def _iter_parent_records(self) -> Iterator[RecordInfo]:
1171
+ """Yield each base RecordInfo registered in the registry, in
1172
+ declaration order. Skips non-NominalType bases and NominalType
1173
+ bases that resolve to protocols or non-record types (builtin
1174
+ markers, primitives) -- those don't appear in `registry.records`.
1175
+ Walks self._record.bases directly because at macro-application
1176
+ time the self RecordInfo's `mro_ancestors` is still empty
1177
+ (populated post-macro-phase).
1178
+ """
1179
+ registry = self._ctx.registry
1180
+ for base in self._record.bases:
1181
+ if not isinstance(base, NominalType):
1182
+ continue
1183
+ parent_info = registry.get_record(base.name)
1184
+ if parent_info is not None:
1185
+ yield parent_info
1186
+
1187
+ def has_method_or_inherited(self, name: str) -> bool:
1188
+ """Like `has_method`, but also walks parent records via the
1189
+ registry. Use when CPython's MRO-based `hasattr` semantics
1190
+ are the right model -- e.g. `@total_ordering` accepting
1191
+ `__lt__` defined on a base class as the anchor.
1192
+ """
1193
+ if self.has_method(name):
1194
+ return True
1195
+ registry = self._ctx.registry
1196
+ for parent_info in self._iter_parent_records():
1197
+ if registry.get_method_overloads_with_parents(parent_info, name):
1198
+ return True
1199
+ return False
1200
+
1201
+ def get_parent_fields(self) -> list[FieldInfo]:
1202
+ """Get all fields from parent record (including inherited).
1203
+
1204
+ Returns fields for the first base that is a registered record,
1205
+ or empty list if no record parent. The caller decides whether
1206
+ to use these based on its own eligibility checks.
1207
+ """
1208
+ for parent_info in self._iter_parent_records():
1209
+ all_parent = self._ctx.registry.get_all_fields(parent_info)
1210
+ return [FieldInfo.from_internal(f) for f in all_parent]
1211
+ return []
1212
+
1213
+ # -- Mutation methods --
1214
+
1215
+ def add_method(self, func: TpyFunction) -> None:
1216
+ """Add a method from a TpyFunction AST node (power user API).
1217
+
1218
+ ``is_macro_generated`` is always set True -- any method added
1219
+ via this API is by definition macro-generated. ``macro_origin``
1220
+ is inherited from the currently-expanding macro (set by the
1221
+ dispatcher when invoking the macro function), so individual
1222
+ synthesis sites don't need to repeat the macro name.
1223
+ """
1224
+ func.is_macro_generated = True
1225
+ if self._macro_origin is not None and func.macro_origin is None:
1226
+ func.macro_origin = self._macro_origin
1227
+ self._added_methods.append(func)
1228
+
1229
+ def add_method_from_source(self, source: str) -> None:
1230
+ """Parse a method definition from TPy source and add it.
1231
+
1232
+ Equivalent to ``self.add_method(ast.quote_fun(source))``.
1233
+ """
1234
+ self.add_method(ast.quote_fun(source))
1235
+
1236
+ def defer_until_macros_complete(
1237
+ self, callback: Callable[['ClassInfo'], None],
1238
+ ) -> None:
1239
+ """Register a callback to run after all eager class macros on
1240
+ this record have applied. Use when the macro's behavior depends
1241
+ on the final method set produced by composition -- e.g.
1242
+ ``@total_ordering`` picks an ordering anchor among the four
1243
+ rich-comparison dunders, some of which a peer macro
1244
+ (``@dataclass(order=True)``) may add. The callback receives a
1245
+ fresh ClassInfo bound to the same record; mutations are written
1246
+ back via the deferred-pass runner.
1247
+ """
1248
+ # Snapshot the origin so the deferred pass attributes the
1249
+ # callback's mutations to the macro that registered it.
1250
+ self._record.pending_deferred_macros.append((self._macro_origin, callback))
1251
+
1252
+ # TODO: property getter+setter emission from macros.
1253
+ # FragmentParser parses fragments one at a time, so the setter's
1254
+ # `@getter_name.setter` decorator has no `property_names` context
1255
+ # and falls through as "Unknown decorator". To support macro-
1256
+ # generated properties, add something like
1257
+ # add_property_pair(getter_source: str, setter_source: str) -> None
1258
+ # that parses both fragments together, threading the getter name
1259
+ # into FragmentParser so the setter resolves correctly. The method
1260
+ # expansion pipeline already handles property-setter `Own[T]`
1261
+ # wrapping, so the fix is purely at the FragmentParser routing
1262
+ # layer. Wait for a real use case before committing to an API shape.
1263
+
1264
+ def set_match_args(self, names: list[str]) -> None:
1265
+ """Set positional match arg names (mirrors Python's __match_args__)."""
1266
+ self._match_args = list(names)
1267
+
1268
+ def is_parent_frozen(self) -> tuple[bool, str] | None:
1269
+ """Check if the first parent record is frozen.
1270
+
1271
+ Returns (is_frozen, parent_name) or None if no record parent.
1272
+ """
1273
+ for parent_info in self._iter_parent_records():
1274
+ return (parent_info.is_frozen, parent_info.name)
1275
+ return None
1276
+
1277
+ def get_method_loc(self, name: str) -> Any:
1278
+ """Get the source location of a method by name."""
1279
+ for m in self._record.methods:
1280
+ if m.name == name:
1281
+ return m.loc or self._record.loc
1282
+ return self._record.loc
1283
+
1284
+ # -- Diagnostics --
1285
+
1286
+ def warning(self, msg: str, loc: Any = None) -> None:
1287
+ """Emit a compiler warning."""
1288
+ self._ctx.warning_from_loc(msg, loc or self._record.loc)
1289
+
1290
+ def error(self, msg: str, loc: Any = None) -> NoReturn:
1291
+ """Raise a compile error."""
1292
+ from .diagnostics import SemanticError
1293
+ raise SemanticError(msg, loc or self._record.loc)
1294
+
1295
+ # -- Apply mutations back to TpyRecord --
1296
+
1297
+ def apply_to_record(self) -> None:
1298
+ """Write macro mutations back to the TpyRecord."""
1299
+ for func in self._added_methods:
1300
+ if func.name == "__init__":
1301
+ self._record.methods.insert(0, func)
1302
+ else:
1303
+ self._record.methods.append(func)
1304
+
1305
+ def get_match_args(self) -> tuple[str, ...] | None:
1306
+ """Return the match args set by the macro, or None."""
1307
+ return tuple(self._match_args) if self._match_args is not None else None
1308
+
1309
+
1310
+ # ---------------------------------------------------------------------------
1311
+ # AstBuilder -- AST node construction for macro modules
1312
+ # ---------------------------------------------------------------------------
1313
+
1314
+ class AstBuilder:
1315
+ """Builder for AST nodes. Macro modules use this instead of importing
1316
+ compiler-internal node types directly.
1317
+
1318
+ Usage: ``from tpyc.macro_api import ast`` then ``ast.name("x")``.
1319
+ """
1320
+
1321
+ def __init__(self) -> None:
1322
+ self._tmp_counter = 0
1323
+
1324
+ def reset_tmp_counter(self) -> None:
1325
+ """Reset the fresh-name counter. Called automatically before each macro invocation."""
1326
+ self._tmp_counter = 0
1327
+
1328
+ def fresh_tmp(self, hint: str = "v") -> str:
1329
+ """Generate a unique temporary variable name."""
1330
+ self._tmp_counter += 1
1331
+ return f"__{hint}_{self._tmp_counter}"
1332
+
1333
+ # -- Expressions --
1334
+
1335
+ def name(self, n: str) -> Expr:
1336
+ """Create a name reference. Dotted names (e.g. 'mylog.infra') are
1337
+ decomposed into a field-access chain so module resolution works."""
1338
+ if not n or ".." in n or n.startswith(".") or n.endswith("."):
1339
+ raise MacroError(f"ast.name(): invalid name {n!r}")
1340
+ if "." not in n:
1341
+ return TpyName(n)
1342
+ parts = n.split(".")
1343
+ result: Expr = TpyName(parts[0])
1344
+ for part in parts[1:]:
1345
+ result = TpyFieldAccess(obj=result, field=part)
1346
+ return result
1347
+
1348
+ def call(self, func: str, args: list[Expr] | None = None,
1349
+ call_type: TpyType | None = None) -> Expr:
1350
+ return TpyCall(func=TpyName(func), args=args or [], call_type=call_type)
1351
+
1352
+ def method_call(self, obj: Expr, method: str, args: list[Expr] | None = None) -> Expr:
1353
+ return TpyMethodCall(obj=obj, method=method, args=args or [])
1354
+
1355
+ def field_access(self, obj: Expr, field: str) -> Expr:
1356
+ return TpyFieldAccess(obj=obj, field=field)
1357
+
1358
+ def binop(self, left: Expr, op: str, right: Expr) -> Expr:
1359
+ return TpyBinOp(left=left, op=op, right=right)
1360
+
1361
+ def subscript(self, obj: Expr, index: Expr) -> Expr:
1362
+ return TpySubscript(obj=obj, index=index)
1363
+
1364
+ def unary(self, op: str, operand: Expr) -> Expr:
1365
+ return TpyUnaryOp(op=op, operand=operand)
1366
+
1367
+ def str_lit(self, s: str) -> Expr:
1368
+ return TpyStrLiteral(value=s)
1369
+
1370
+ def int_lit(self, n: int) -> Expr:
1371
+ return TpyIntLiteral(value=n)
1372
+
1373
+ def float_lit(self, f: float) -> Expr:
1374
+ return TpyFloatLiteral(value=f)
1375
+
1376
+ def bool_lit(self, b: bool) -> Expr:
1377
+ return TpyBoolLiteral(value=b)
1378
+
1379
+ def none_lit(self) -> Expr:
1380
+ return TpyNoneLiteral()
1381
+
1382
+ def list_lit(self, elements: list[Expr] | None = None) -> Expr:
1383
+ return TpyArrayLiteral(elements=elements or [])
1384
+
1385
+ def dict_lit(self, keys: list[Expr] | None = None, values: list[Expr] | None = None) -> Expr:
1386
+ return TpyDictLiteral(keys=keys or [], values=values or [])
1387
+
1388
+ def tuple_lit(self, elements: list[Expr]) -> Expr:
1389
+ return TpyTupleLiteral(elements=elements)
1390
+
1391
+ def list_comprehension(self, element_expr: Expr,
1392
+ generator: TpyComprehensionGenerator) -> Expr:
1393
+ return TpyListComprehension(element_expr=element_expr, generator=generator)
1394
+
1395
+ def dict_comprehension(self, key_expr: Expr, value_expr: Expr,
1396
+ generator: TpyComprehensionGenerator) -> Expr:
1397
+ return TpyDictComprehension(key_expr=key_expr, value_expr=value_expr,
1398
+ generator=generator)
1399
+
1400
+ def set_comprehension(self, element_expr: Expr,
1401
+ generator: TpyComprehensionGenerator) -> Expr:
1402
+ return TpySetComprehension(element_expr=element_expr, generator=generator)
1403
+
1404
+ def if_expr(self, condition: Expr, then_expr: Expr, else_expr: Expr) -> Expr:
1405
+ return TpyIfExpr(condition=condition, then_expr=then_expr, else_expr=else_expr)
1406
+
1407
+ def comprehension_generator(self, var: str, iterable: Expr,
1408
+ conditions: list[Expr] | None = None,
1409
+ unpack_vars: list[str | None] | None = None) -> TpyComprehensionGenerator:
1410
+ return TpyComprehensionGenerator(var=var, iterable=iterable,
1411
+ conditions=conditions or [],
1412
+ unpack_vars=unpack_vars)
1413
+
1414
+ # -- Statements --
1415
+
1416
+ def var_decl(self, name: str, type: TpyType | None = None,
1417
+ init: Expr | None = None) -> Stmt:
1418
+ return TpyVarDecl(name=name, type=type, init=init)
1419
+
1420
+ def assign(self, target: Expr, value: Expr) -> Stmt:
1421
+ return TpyAssign(target=target, value=value)
1422
+
1423
+ def expr_stmt(self, expr: Expr) -> Stmt:
1424
+ return TpyExprStmt(expr=expr)
1425
+
1426
+ def return_(self, value: Expr | None = None) -> Stmt:
1427
+ return TpyReturn(value=value)
1428
+
1429
+ def if_(self, condition: Expr, then_body: list[Stmt],
1430
+ else_body: list[Stmt] | None = None) -> Stmt:
1431
+ return TpyIf(condition=condition, then_body=then_body,
1432
+ else_body=else_body or [])
1433
+
1434
+ def while_(self, condition: Expr, body: list[Stmt]) -> Stmt:
1435
+ return TpyWhile(condition=condition, body=body)
1436
+
1437
+ def for_each(self, var: str, iterable: Expr, body: list[Stmt],
1438
+ is_tuple_unpack: bool = False) -> Stmt:
1439
+ return TpyForEach(var=var, iterable=iterable, body=body,
1440
+ is_tuple_unpack=is_tuple_unpack)
1441
+
1442
+ def raise_(self, exception_type: str, args: list[Expr] | None = None) -> Stmt:
1443
+ if args:
1444
+ return TpyRaise(exception_type=exception_type, args=args, is_call_form=True)
1445
+ return TpyRaise(exception_type=exception_type)
1446
+
1447
+ def assert_(self, condition: Expr, message: Expr | None = None) -> Stmt:
1448
+ return TpyAssert(condition=condition, message=message)
1449
+
1450
+ def try_(self, try_body: list[Stmt], handlers: list | None = None,
1451
+ else_body: list[Stmt] | None = None,
1452
+ finally_body: list[Stmt] | None = None) -> Stmt:
1453
+ return TpyTry(try_body=try_body, handlers=handlers or [],
1454
+ else_body=else_body or [], finally_body=finally_body or [])
1455
+
1456
+ def except_handler(self, exception_type: str | None = None,
1457
+ binding: str | None = None,
1458
+ body: list[Stmt] | None = None) -> TpyExceptHandler:
1459
+ return TpyExceptHandler(exception_type=exception_type,
1460
+ binding=binding, body=body or [])
1461
+
1462
+ def match(self, subject: Expr, cases: list) -> Stmt:
1463
+ return TpyMatch(subject=subject, cases=cases)
1464
+
1465
+ def match_case(self, pattern: Any, body: list[Stmt],
1466
+ guard: Expr | None = None) -> TpyMatchCase:
1467
+ return TpyMatchCase(pattern=pattern, guard=guard, body=body)
1468
+
1469
+ def literal_pattern(self, value: int | float | str | bool | None) -> TpyLiteralPattern:
1470
+ return TpyLiteralPattern(value=value)
1471
+
1472
+ def wildcard_pattern(self) -> TpyWildcardPattern:
1473
+ return TpyWildcardPattern()
1474
+
1475
+ def tuple_unpack(self, targets: list[str | None], value: Expr) -> Stmt:
1476
+ return TpyTupleUnpack(targets=targets, value=value)
1477
+
1478
+ # -- Cloning --
1479
+
1480
+ def clone(self, expr: Expr) -> Expr:
1481
+ """Deep-copy an expression tree. Use when the same logical expression
1482
+ must appear in multiple places (e.g. condition and body of a ternary)
1483
+ to avoid sema overwriting resolved types on shared nodes."""
1484
+ return copy.deepcopy(expr)
1485
+
1486
+ # -- Introspection --
1487
+
1488
+ def get_field_name(self, expr: Expr) -> str | None:
1489
+ """If expr is a field access node, return the field name. Otherwise None."""
1490
+ if isinstance(expr, TpyFieldAccess):
1491
+ return expr.field
1492
+ return None
1493
+
1494
+ def get_name(self, expr: Expr) -> str | None:
1495
+ """If expr is a simple name node, return the name. Otherwise None."""
1496
+ if isinstance(expr, TpyName):
1497
+ return expr.name
1498
+ return None
1499
+
1500
+ # -- Functions --
1501
+
1502
+ def function(self, name: str, params: list[tuple[str, TpyType]],
1503
+ return_type: TpyType, body: list[Stmt], *,
1504
+ is_method: bool = False, is_staticmethod: bool = False,
1505
+ is_readonly: bool = False, readonly_opt_out: bool = False,
1506
+ error_return: str | None = None,
1507
+ defaults: list[Expr | None] | None = None,
1508
+ hides_parent: bool = False) -> Function:
1509
+ return TpyFunction(
1510
+ name=name, params=params, return_type=return_type, body=body,
1511
+ is_method=is_method, is_staticmethod=is_staticmethod,
1512
+ is_readonly=is_readonly, readonly_opt_out=readonly_opt_out,
1513
+ error_return=error_return, defaults=defaults or [],
1514
+ hides_parent=hides_parent,
1515
+ )
1516
+
1517
+ # -- Source-based quoting --
1518
+
1519
+ def _quote(
1520
+ self, source: str, kind: Literal["function", "statements", "expression"],
1521
+ ) -> list[Stmt] | Expr | Function:
1522
+ try:
1523
+ return _FragmentParser.parse_fragment(source, kind=kind)
1524
+ except SyntaxError as e:
1525
+ clean = textwrap.dedent(source).strip()
1526
+ lines = clean.splitlines()
1527
+ lineno = (e.lineno or 1) - 1
1528
+ bad_line = lines[lineno].strip() if 0 <= lineno < len(lines) else lines[0]
1529
+ raise MacroError(f"syntax error in quoted source: `{bad_line}`") from None
1530
+ except _ParseError as e:
1531
+ preview = textwrap.dedent(source).strip().split("\n")[0]
1532
+ if len(preview) > 60:
1533
+ preview = preview[:57] + "..."
1534
+ raise MacroError(f"{e} (source: `{preview}`)") from None
1535
+
1536
+ def quote(self, source: str) -> list[Stmt]:
1537
+ """Parse TPy statements from a source string."""
1538
+ return self._quote(source, "statements")
1539
+
1540
+ def quote_expr(self, source: str) -> Expr:
1541
+ """Parse a single TPy expression from a source string."""
1542
+ return self._quote(source, "expression")
1543
+
1544
+ def quote_fun(self, source: str) -> Function:
1545
+ """Parse a complete function definition from a source string.
1546
+
1547
+ Decorators are not supported. To mark as staticmethod, set
1548
+ func.is_staticmethod = True on the returned function.
1549
+ """
1550
+ return self._quote(source, "function")
1551
+
1552
+
1553
+ # Module-level singleton
1554
+ ast = AstBuilder()
1555
+
1556
+
1557
+ # ---------------------------------------------------------------------------
1558
+ # TypeBuilder -- type construction for macro modules
1559
+ # ---------------------------------------------------------------------------
1560
+
1561
+ class TypeBuilder:
1562
+ """Builder for TpyType objects. Macro modules use this instead of importing
1563
+ compiler-internal type classes directly.
1564
+
1565
+ Usage: ``from tpyc.macro_api import types`` then ``types.named("Foo")``.
1566
+ """
1567
+
1568
+ # -- Constructors --
1569
+
1570
+ def named(self, name: str) -> TpyType:
1571
+ return NominalType(name)
1572
+
1573
+ def own(self, inner: TpyType) -> TpyType:
1574
+ return OwnType(inner)
1575
+
1576
+ def optional(self, inner: TpyType) -> TpyType:
1577
+ return OptionalType(inner)
1578
+
1579
+ def list(self, element_type: TpyType) -> TpyType:
1580
+ return make_list(element_type)
1581
+
1582
+ def span(self, element_type: TpyType) -> TpyType:
1583
+ return make_span(element_type)
1584
+
1585
+ def dict(self, key_type: TpyType, value_type: TpyType) -> TpyType:
1586
+ return make_dict(key_type, value_type)
1587
+
1588
+ def set(self, element_type: TpyType) -> TpyType:
1589
+ return make_set(element_type)
1590
+
1591
+ def tuple(self, element_types: tuple[TpyType, ...] | list[TpyType]) -> TpyType:
1592
+ if isinstance(element_types, list):
1593
+ element_types = tuple(element_types)
1594
+ return TupleType(element_types)
1595
+
1596
+ def union(self, member_types: tuple[TpyType, ...] | list[TpyType]) -> TpyType:
1597
+ if isinstance(member_types, list):
1598
+ member_types = tuple(member_types)
1599
+ return UnionType(member_types)
1600
+
1601
+ # -- Singleton types --
1602
+
1603
+ @property
1604
+ def void(self) -> TpyType:
1605
+ return _VOID
1606
+
1607
+ @property
1608
+ def str(self) -> TpyType:
1609
+ return _STR
1610
+
1611
+ @property
1612
+ def str_view(self) -> TpyType:
1613
+ return _STRVIEW
1614
+
1615
+ @property
1616
+ def bool(self) -> TpyType:
1617
+ return _BOOL
1618
+
1619
+ # -- Fixed-width integers --
1620
+
1621
+ @property
1622
+ def int8(self) -> TpyType:
1623
+ return INT8
1624
+
1625
+ @property
1626
+ def int16(self) -> TpyType:
1627
+ return INT16
1628
+
1629
+ @property
1630
+ def int32(self) -> TpyType:
1631
+ return INT32
1632
+
1633
+ @property
1634
+ def int64(self) -> TpyType:
1635
+ return INT64
1636
+
1637
+ @property
1638
+ def uint8(self) -> TpyType:
1639
+ return UINT8
1640
+
1641
+ @property
1642
+ def uint16(self) -> TpyType:
1643
+ return UINT16
1644
+
1645
+ @property
1646
+ def uint32(self) -> TpyType:
1647
+ return UINT32
1648
+
1649
+ @property
1650
+ def uint64(self) -> TpyType:
1651
+ return UINT64
1652
+
1653
+ # -- Floating point --
1654
+
1655
+ @property
1656
+ def float(self) -> TpyType:
1657
+ return _FLOAT
1658
+
1659
+ @property
1660
+ def float64(self) -> TpyType:
1661
+ return _FLOAT
1662
+
1663
+ @property
1664
+ def float32(self) -> TpyType:
1665
+ return _FLOAT32
1666
+
1667
+ # -- Arbitrary precision --
1668
+
1669
+ @property
1670
+ def bigint(self) -> TpyType:
1671
+ return _BIGINT
1672
+
1673
+
1674
+ # Module-level singleton
1675
+ types = TypeBuilder()
1676
+
1677
+
1678
+ # ---------------------------------------------------------------------------
1679
+ # AST builder helpers for common macro patterns
1680
+ # ---------------------------------------------------------------------------
1681
+
1682
+ def _wrap_literal(value: object) -> TpyExpr | None:
1683
+ """Wrap a plain Python value in a TpyExpr node. Pass through TpyExpr unchanged."""
1684
+ if isinstance(value, TpyExpr):
1685
+ return value
1686
+ if value is None:
1687
+ return TpyNoneLiteral()
1688
+ if isinstance(value, bool):
1689
+ return TpyBoolLiteral(value=value)
1690
+ if isinstance(value, int):
1691
+ return TpyIntLiteral(value=value)
1692
+ if isinstance(value, float):
1693
+ return TpyFloatLiteral(value=value)
1694
+ if isinstance(value, str):
1695
+ return TpyStrLiteral(value=value)
1696
+ return value # non-literal (e.g. factory callable)
1697
+
1698
+
1699
+ def expr_to_cpp_default(expr: TpyExpr | int | float | str | bool | None) -> str | None:
1700
+ """Convert a TpyExpr or plain value to a C++ default value literal string, or None."""
1701
+ # Normalize plain Python values to TpyExpr first
1702
+ if not isinstance(expr, TpyExpr) and expr is not None:
1703
+ expr = _wrap_literal(expr)
1704
+ if isinstance(expr, TpyIntLiteral):
1705
+ return str(expr.value)
1706
+ if isinstance(expr, TpyFloatLiteral):
1707
+ return str(expr.value)
1708
+ if isinstance(expr, TpyBoolLiteral):
1709
+ return "true" if expr.value else "false"
1710
+ if isinstance(expr, TpyStrLiteral):
1711
+ escaped = expr.value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
1712
+ return f'"{escaped}"'
1713
+ if isinstance(expr, TpyNoneLiteral):
1714
+ return "std::nullopt"
1715
+ if isinstance(expr, TpyUnaryOp) and expr.op == "-":
1716
+ inner = expr_to_cpp_default(expr.operand)
1717
+ if inner is not None:
1718
+ return f"-{inner}"
1719
+ if isinstance(expr, TpyCall) and isinstance(expr.func, TpyName) and expr.func_name in _FIXED_INT_NAMES:
1720
+ if not expr.args:
1721
+ return "0"
1722
+ if len(expr.args) == 1:
1723
+ return expr_to_cpp_default(expr.args[0])
1724
+ return None