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,907 @@
1
+ """
2
+ TurboPython Protocol/Concept Code Generation
3
+
4
+ C++20 concept generation from TurboPython protocols and template header utilities.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from collections import namedtuple
9
+ from typing import TextIO, TYPE_CHECKING
10
+
11
+ from ..typesys import (
12
+ TpyType, NominalType, TypeParamRef, TypeParamKind, ReadonlyType, VoidType, SelfType,
13
+ OptionalType, UnionType, OwnType, MethodSignature, is_protocol_type,
14
+ unwrap_readonly, unwrap_own, is_protocol_union, protocol_union_protocols,
15
+ protocol_union_has_none, unwrap_ref_type,
16
+ )
17
+ from ..parse import TpyProtocol, TpyRecord
18
+ from .context import INDENT, DUNDER_TO_BINARY_OP, CodeGenError, qualified_cpp_name
19
+ from ..type_def_registry import is_str_type, protocol_info_of, is_subtype
20
+ from ..symbol_binding import lookup_imported, SymbolKind
21
+
22
+ if TYPE_CHECKING:
23
+ from .context import CodeGenContext
24
+
25
+ # Unified protocol parameter info: name, list of protocol types (1+), nullable flag
26
+ ProtocolParamInfo = namedtuple('ProtocolParamInfo', ['name', 'protocols', 'has_none'])
27
+
28
+ # Adapter/RefAdapter template parameter name for the concrete impl type.
29
+ # `_GENERIC` is used inside the partial-spec template that's parameterized
30
+ # over the protocol's own type params; it uses the reserved `__tpy_` prefix
31
+ # to avoid colliding with any user-chosen identifier. `_NON_GENERIC` is the
32
+ # historical name kept for snapshot stability when the protocol has no type
33
+ # params.
34
+ _ADAPTER_IMPL_GENERIC = "__tpy_Impl"
35
+ _ADAPTER_IMPL_NON_GENERIC = "T"
36
+
37
+
38
+ class ProtocolGenerator:
39
+ """Generates C++20 concepts from TurboPython protocols."""
40
+
41
+ def __init__(self, ctx: CodeGenContext):
42
+ self.ctx = ctx
43
+
44
+ def resolve_type_for_codegen(self, typ: TpyType) -> TpyType:
45
+ """Resolve a type, setting is_protocol flag if it's actually a protocol.
46
+
47
+ During parsing, some types may be classified as records when they're
48
+ actually protocols (e.g., imported protocols). This method fixes that for codegen.
49
+ """
50
+ if isinstance(typ, NominalType) and not typ.is_protocol:
51
+ # Check if this is actually a protocol. Probe with the protocol
52
+ # flag set so `protocol_info_of`'s qname-derived TypeDef lookup
53
+ # matches the authoritative `{module}.{name}` key.
54
+ probe = typ.with_protocol_flag(is_protocol=True)
55
+ if protocol_info_of(probe) is not None:
56
+ return probe
57
+ return typ
58
+
59
+ def is_static_protocol_param(self, typ: TpyType) -> bool:
60
+ """Check if a type is a static protocol parameter (single or union, optional or not).
61
+
62
+ Returns True for:
63
+ - NominalType that is a static protocol
64
+ - OptionalType(static protocol)
65
+ - UnionType of 2+ static protocols (optionally with None)
66
+ Excludes @dynamic protocols.
67
+ """
68
+ unwrapped = unwrap_own(unwrap_readonly(unwrap_ref_type(typ)))
69
+ # Optional[Protocol]
70
+ if isinstance(unwrapped, OptionalType):
71
+ resolved = self.resolve_type_for_codegen(unwrapped.inner)
72
+ if is_protocol_type(resolved):
73
+ protocol_info = protocol_info_of(resolved)
74
+ if protocol_info and protocol_info.is_dynamic:
75
+ return False
76
+ return True
77
+ return False
78
+ # Protocol union (2+ protocols, optionally with None)
79
+ if isinstance(unwrapped, UnionType):
80
+ resolved_members = tuple(self.resolve_type_for_codegen(m) for m in unwrapped.members)
81
+ resolved_union = UnionType(resolved_members)
82
+ return is_protocol_union(resolved_union)
83
+ # Single bare protocol
84
+ resolved = self.resolve_type_for_codegen(unwrapped)
85
+ if is_protocol_type(resolved):
86
+ protocol_info = protocol_info_of(resolved)
87
+ if protocol_info and protocol_info.is_dynamic:
88
+ return False
89
+ return True
90
+ return False
91
+
92
+ def get_all_protocol_params(
93
+ self, params: list[tuple[str, TpyType]]
94
+ ) -> list[ProtocolParamInfo]:
95
+ """Get unified list of all static protocol parameters.
96
+
97
+ Handles single protocols (bare or Optional) and protocol unions.
98
+ Returns ProtocolParamInfo(name, protocols, has_none) for each.
99
+ """
100
+ result: list[ProtocolParamInfo] = []
101
+ for pname, ptype in params:
102
+ unwrapped = unwrap_own(unwrap_readonly(unwrap_ref_type(ptype)))
103
+
104
+ # Optional[Protocol] -- single protocol, nullable
105
+ if isinstance(unwrapped, OptionalType):
106
+ resolved = self.resolve_type_for_codegen(unwrapped.inner)
107
+ if is_protocol_type(resolved) and isinstance(resolved, NominalType):
108
+ protocol_info = protocol_info_of(resolved)
109
+ if protocol_info and protocol_info.is_dynamic:
110
+ continue
111
+ result.append(ProtocolParamInfo(pname, [resolved], True))
112
+ continue
113
+
114
+ # Protocol union (2+ protocols, optionally with None)
115
+ if isinstance(unwrapped, UnionType):
116
+ resolved_members = tuple(self.resolve_type_for_codegen(m) for m in unwrapped.members)
117
+ resolved_union = UnionType(resolved_members)
118
+ if is_protocol_union(resolved_union):
119
+ protos = [m for m in protocol_union_protocols(resolved_union) if isinstance(m, NominalType)]
120
+ has_none = protocol_union_has_none(resolved_union)
121
+ result.append(ProtocolParamInfo(pname, protos, has_none))
122
+ continue
123
+
124
+ # Single bare protocol
125
+ resolved = self.resolve_type_for_codegen(unwrapped)
126
+ if is_protocol_type(resolved) and isinstance(resolved, NominalType):
127
+ protocol_info = protocol_info_of(resolved)
128
+ if protocol_info and protocol_info.is_dynamic:
129
+ continue
130
+ result.append(ProtocolParamInfo(pname, [resolved], False))
131
+
132
+ return result
133
+
134
+ def get_concept_name(self, protocol: NominalType) -> str:
135
+ """Get the C++ concept name for a protocol type.
136
+
137
+ For @dynamic protocols the concept is renamed to __{Name}_Concept__
138
+ so the clean name is free for the base class struct.
139
+ Non-dynamic protocols keep their original name.
140
+ """
141
+ # Unified lookup via registry
142
+ protocol_info = protocol_info_of(protocol)
143
+ if protocol_info and protocol_info.cpp_concept:
144
+ return protocol_info.cpp_concept
145
+ is_dynamic = protocol_info is not None and protocol_info.is_dynamic
146
+ # Check if this is an imported protocol from another user module
147
+ qual = lookup_imported(
148
+ self.ctx.analyzer.ctx.module_attributes, protocol.name,
149
+ SymbolKind.PROTOCOL_STATIC, SymbolKind.PROTOCOL_DYNAMIC)
150
+ if qual is not None:
151
+ source_module, original_name = qual
152
+ cpp_name = f"__{original_name}_Concept__" if is_dynamic else original_name
153
+ return qualified_cpp_name(source_module, cpp_name)
154
+ # Protocol from a bare-imported module (import typing + typing.Sized)
155
+ if protocol_info and protocol_info.module and protocol_info.module in self.ctx.user_module_imports:
156
+ cpp_name = f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
157
+ return qualified_cpp_name(protocol_info.module, cpp_name)
158
+ # Protocol from an implicit stdlib module (typing, tpy) -- needs qualified name
159
+ if protocol_info and protocol_info.module and protocol_info.module in self.ctx.all_user_modules:
160
+ cpp_name = f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
161
+ return qualified_cpp_name(protocol_info.module, cpp_name)
162
+ # Protocol defined in the current module (or unknown origin fallback)
163
+ return f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
164
+
165
+ def get_dynamic_base_name(self, protocol: NominalType) -> str:
166
+ """Get the (possibly qualified) C++ base class name for a @dynamic protocol.
167
+
168
+ Type args render through `t.to_cpp()`; for unresolved TypeParamRef
169
+ args (inside a nested template emission) this is the bare name `T`,
170
+ so the same helper covers both call-site instantiations
171
+ (`Awaitable<int32_t>`) and nested-template references (`Awaitable<T>`).
172
+
173
+ For protocols that are both @dynamic and @native (cpp_concept set),
174
+ the C++ class name comes from the @native annotation -- the abstract
175
+ base lives in a runtime header rather than codegen output. Used by
176
+ Throwable (`@native("tpy::Throwable") @dynamic`) to bridge to
177
+ `runtime/cpp/include/tpy/throwable.hpp::tpy::Throwable`.
178
+ """
179
+ protocol_info = protocol_info_of(protocol)
180
+ if protocol_info and protocol_info.cpp_concept:
181
+ base = protocol_info.cpp_concept
182
+ if protocol.type_args:
183
+ args_cpp = ", ".join(t.to_cpp() for t in protocol.type_args)
184
+ return f"{base}<{args_cpp}>"
185
+ return base
186
+ name = protocol.name
187
+ qual = lookup_imported(
188
+ self.ctx.analyzer.ctx.module_attributes, name,
189
+ SymbolKind.PROTOCOL_STATIC, SymbolKind.PROTOCOL_DYNAMIC)
190
+ if qual is not None:
191
+ source_module, original_name = qual
192
+ base = qualified_cpp_name(source_module, original_name)
193
+ else:
194
+ base = self._qualify_protocol_via_qname(protocol) or name
195
+ if protocol.type_args:
196
+ args_cpp = ", ".join(t.to_cpp() for t in protocol.type_args)
197
+ return f"{base}<{args_cpp}>"
198
+ return base
199
+
200
+ def _qualify_protocol_via_qname(self, protocol: NominalType) -> str | None:
201
+ """Fallback qualification for protocols not bound in the current
202
+ module's attributes -- e.g. implicit-stdlib protocols referenced
203
+ only through a sema-synthesized type like `Cancellable[T]`
204
+ constructed by `make_cancellable` for async-def returns. Skips
205
+ when the protocol lives in the current module (the bare local
206
+ name is correct there)."""
207
+ qname = protocol._module_qname
208
+ if not qname or "." not in qname:
209
+ return None
210
+ source_module, original_name = qname.rsplit(".", 1)
211
+ if source_module == self.ctx.analyzer.ctx.module_name:
212
+ return None
213
+ return qualified_cpp_name(source_module, original_name)
214
+
215
+ def get_dynamic_adapter_type(self, protocol: NominalType, concrete_cpp: str) -> str:
216
+ """Get the full C++ type for an owning adapter: ::tpy::Adapter<Base, Concrete>."""
217
+ base = self.get_dynamic_base_name(protocol)
218
+ return f"::tpy::Adapter<{base}, {concrete_cpp}>"
219
+
220
+ def get_dynamic_ref_adapter_type(self, protocol: NominalType, concrete_cpp: str) -> str:
221
+ """Get the full C++ type for a ref adapter: ::tpy::RefAdapter<Base, Concrete>."""
222
+ base = self.get_dynamic_base_name(protocol)
223
+ return f"::tpy::RefAdapter<{base}, {concrete_cpp}>"
224
+
225
+ def dynamic_narrow_cast_rhs(
226
+ self, cpp_type: str, check_type: TpyType, source_inner: 'TpyType | None',
227
+ cast_arg: str, *, is_const: bool,
228
+ ) -> str:
229
+ """Build the pointer-producing RHS that narrows `cast_arg` (a
230
+ `[const] P*` payload pointer) to a `[const] Sub*` for isinstance
231
+ dispatch. The result is used as the if-init / extraction-local
232
+ initializer and, against nullptr, as the branch condition.
233
+
234
+ An inheritance conformer (or a polymorphic-class root, which has no
235
+ structural conformers) uses `dynamic_cast`. A STRUCTURAL conformer of
236
+ a @dynamic protocol routes through `tpy::dyn_adapter_cast`, which
237
+ unwraps the `Adapter`/`RefAdapter` the structural value sits inside --
238
+ a plain `dynamic_cast<Sub*>` would always fail there. The structural
239
+ overload infers const-ness from `cast_arg`, so `is_const` only shapes
240
+ the `dynamic_cast` form."""
241
+ if (is_protocol_type(source_inner)
242
+ and isinstance(check_type, NominalType)
243
+ and not self.directly_implements_dynamic(check_type, source_inner)):
244
+ base = self.get_dynamic_base_name(source_inner)
245
+ return f"::tpy::dyn_adapter_cast<{base}, {cpp_type}>({cast_arg})"
246
+ const_pfx = "const " if is_const else ""
247
+ return f"dynamic_cast<{const_pfx}{cpp_type}*>({cast_arg})"
248
+
249
+ def dyn_protocol_forward_ok(self, source: TpyType, target: TpyType) -> bool:
250
+ """True if `source` (@dynamic protocol) can be forwarded as `target`
251
+ (@dynamic protocol) without an Adapter wrap -- same protocol (joint
252
+ qualified_name + type_args check, so generic instantiations like
253
+ `Container[int]` and `Container[str]` stay distinct) or inheriting peer.
254
+ """
255
+ if not (isinstance(source, NominalType) and isinstance(target, NominalType)):
256
+ return False
257
+ if (source.qualified_name() == target.qualified_name()
258
+ and source.type_args == target.type_args):
259
+ return True
260
+ return is_subtype(
261
+ self.ctx.analyzer.registry.scan_by_short_name(source.name), target.name
262
+ )
263
+
264
+ def directly_implements_dynamic(self, concrete_type: TpyType, protocol: NominalType) -> bool:
265
+ """Check if concrete_type inherits a @dynamic protocol (directly or transitively).
266
+
267
+ Returns True when the record explicitly implements a @dynamic protocol that
268
+ is (or transitively inherits from) `protocol`. This means the C++ struct
269
+ inherits the protocol base class through the inheritance chain and no adapter
270
+ wrapping is needed.
271
+
272
+ Match is by short name; type_args are ignored. Cross-instantiation
273
+ mismatches (e.g. assigning `Container[Int32]` into `Container[str]`)
274
+ are already rejected by sema before this helper runs.
275
+
276
+ The ``is_dynamic`` filter is applied at the IMPLEMENTED-protocol level
277
+ (chain root), not at the target. A non-@dynamic protocol contributes no
278
+ C++ base, so a chain rooted in a non-@dynamic implemented protocol
279
+ cannot make ``concrete_type`` C++-inherit anything -- even if a
280
+ @dynamic ancestor sits further up the chain. This differs from
281
+ ``sema/type_ops._inherits_protocol`` (which filters on the target,
282
+ not the root); the closure cached on ``RecordInfo.transitive_supertypes``
283
+ encodes the unfiltered closure used by the latter, so this helper keeps
284
+ an explicit per-element walk and uses ``is_subtype`` only for the
285
+ per-protocol ancestor step.
286
+
287
+ @native records are excluded: their C++ representation is opaque to
288
+ codegen (the struct is hand-written elsewhere), so even when the TPy
289
+ declaration claims `class NativeRec(SomeDynProto)`, the C++ struct
290
+ almost certainly does NOT inherit the codegen-emitted protocol base.
291
+ Routing through Adapter is the only safe lowering for those types.
292
+ Example: `BaseException` inherits `Throwable` at the TPy level for
293
+ polymorphism dispatch on `Optional[BaseException]`, but
294
+ `::tpy::BaseException` in core.hpp does not inherit `tpystd::tpy::Throwable`.
295
+ """
296
+ if not isinstance(concrete_type, NominalType) or not concrete_type.is_user_record:
297
+ return False
298
+ record_info = self.ctx.analyzer.registry.get_record(concrete_type.name)
299
+ if record_info is None or record_info.is_native:
300
+ return False
301
+ proto_name = protocol.name
302
+ for p in record_info.implemented_protocols:
303
+ pi = protocol_info_of(p)
304
+ if pi is None or not pi.is_dynamic:
305
+ continue
306
+ if p.name == proto_name or is_subtype(pi, proto_name):
307
+ return True
308
+ return False
309
+
310
+ def gen_record_template_parts(
311
+ self,
312
+ type_params: list[str],
313
+ type_param_bounds: dict[str, NominalType],
314
+ type_param_kinds: list[TypeParamKind] | None = None
315
+ ) -> list[str]:
316
+ """Render the per-param parts of a generic record's template header.
317
+
318
+ For unbounded TYPE params: `typename T`
319
+ For protocol-bounded TYPE params: `Comparable T` / `Sequence<int32_t> T`
320
+ For INT params: `std::size_t N`
321
+ For class / type-param bounds (not C++ concepts): `typename T` -- the
322
+ TPy-side subtype bound is enforced at the call site (satisfies_bound).
323
+ """
324
+ template_parts = []
325
+ for i, tp in enumerate(type_params):
326
+ kind = type_param_kinds[i] if type_param_kinds and i < len(type_param_kinds) else TypeParamKind.TYPE
327
+ if kind == TypeParamKind.INT:
328
+ template_parts.append(f"std::size_t {tp}")
329
+ elif tp in type_param_bounds and is_protocol_type(type_param_bounds[tp]):
330
+ bound = type_param_bounds[tp]
331
+ concept_name = self.get_concept_name(bound)
332
+ if bound.type_args:
333
+ type_args_cpp = ", ".join(t.to_cpp() for t in bound.type_args)
334
+ template_parts.append(f"{concept_name}<{type_args_cpp}> {tp}")
335
+ else:
336
+ template_parts.append(f"{concept_name} {tp}")
337
+ else:
338
+ template_parts.append(f"typename {tp}")
339
+ return template_parts
340
+
341
+ def gen_record_template_header(
342
+ self,
343
+ type_params: list[str],
344
+ type_param_bounds: dict[str, NominalType],
345
+ type_param_kinds: list[TypeParamKind] | None = None
346
+ ) -> str:
347
+ parts = self.gen_record_template_parts(type_params, type_param_bounds, type_param_kinds)
348
+ return f"template<{', '.join(parts)}>"
349
+
350
+ def _concept_constraint(self, pname: str, ptype: NominalType) -> str:
351
+ """Build the concept constraint expression for a protocol template param.
352
+
353
+ Returns e.g. '::tpy::Sized<T_items>' or '::tpy::Sequence<T_items, int32_t>'.
354
+ """
355
+ concept_name = self.get_concept_name(ptype)
356
+ if ptype.type_args:
357
+ type_args_cpp = ", ".join(t.to_cpp() for t in ptype.type_args)
358
+ return f"{concept_name}<T_{pname}, {type_args_cpp}>"
359
+ return f"{concept_name}<T_{pname}>"
360
+
361
+ def gen_combined_template_header(
362
+ self,
363
+ type_params: list[str],
364
+ protocol_params: list[ProtocolParamInfo],
365
+ type_param_bounds: dict[str, NominalType] | None = None,
366
+ *, emit_defaults: bool = True,
367
+ ) -> str:
368
+ """Generate template header combining type parameters and concept constraints.
369
+
370
+ For generic functions: template<typename T>
371
+ For generic functions with protocols: template<typename T, ::tpy::Sized T_items>
372
+ For bounded type params: template<Comparable T>
373
+ For protocol unions: template<typename T_items> requires (A<T_items> || B<T_items>)
374
+ """
375
+ template_parts = []
376
+
377
+ # Add type parameters for generic functions (with optional bounds)
378
+ for tp in type_params:
379
+ if (type_param_bounds and tp in type_param_bounds
380
+ and is_protocol_type(type_param_bounds[tp])):
381
+ bound = type_param_bounds[tp]
382
+ concept_name = self.get_concept_name(bound)
383
+ if bound.type_args:
384
+ type_args_cpp = ", ".join(t.to_cpp() for t in bound.type_args)
385
+ template_parts.append(f"{concept_name}<{type_args_cpp}> {tp}")
386
+ else:
387
+ template_parts.append(f"{concept_name} {tp}")
388
+ else:
389
+ # A class or type-param bound (`U: Animal` / `U: T`) is not a
390
+ # C++ concept; emit an unconstrained param. The subtype bound
391
+ # is enforced at the TPy call site (satisfies_bound).
392
+ template_parts.append(f"typename {tp}")
393
+
394
+ # Add protocol params with concept constraints
395
+ requires_parts = []
396
+ for info in protocol_params:
397
+ pname = info.name
398
+ # Single required protocol: clean `Concept T_x` syntax
399
+ if len(info.protocols) == 1 and not info.has_none:
400
+ ptype = info.protocols[0]
401
+ concept_name = self.get_concept_name(ptype)
402
+ if ptype.type_args:
403
+ type_args_cpp = ", ".join(t.to_cpp() for t in ptype.type_args)
404
+ template_parts.append(f"{concept_name}<{type_args_cpp}> T_{pname}")
405
+ else:
406
+ template_parts.append(f"{concept_name} T_{pname}")
407
+ else:
408
+ # Multiple protocols or nullable: typename + requires clause.
409
+ # `nullptr_t` here is the *nullable-static-protocol-param*
410
+ # sentinel (e.g. `def f(p: Sized | None)` -- the param accepts
411
+ # nullptr at the C++ level so `f(None)` lowers to a call with
412
+ # no arg). This is distinct from `None` as a value-bearing
413
+ # generic type argument (which lowers to std::monostate via
414
+ # NoneType.to_cpp): the protocol-template default is matched
415
+ # by passing literal `nullptr` and gated by `if constexpr`
416
+ # bodies, never stored as a value.
417
+ default_part = " = std::nullptr_t" if info.has_none and emit_defaults else ""
418
+ template_parts.append(f"typename T_{pname}{default_part}")
419
+ constraints = []
420
+ if info.has_none:
421
+ constraints.append(f"std::same_as<T_{pname}, std::nullptr_t>")
422
+ for proto in info.protocols:
423
+ constraints.append(self._concept_constraint(pname, proto))
424
+ requires_parts.append(f"({' || '.join(constraints)})")
425
+
426
+ if not template_parts:
427
+ return ""
428
+ result = f"template<{', '.join(template_parts)}>\n"
429
+ if requires_parts:
430
+ result += f" requires {' && '.join(requires_parts)}\n"
431
+ return result
432
+
433
+ def collect_concept_methods(self, protocol_name: str) -> list[MethodSignature]:
434
+ return self.ctx.analyzer.protocols.collect_protocol_methods(protocol_name)
435
+
436
+ def is_protocol_const(self, protocol_name: str) -> bool:
437
+ """Check if a protocol is functionally const (all methods are readonly).
438
+
439
+ True when the protocol-level is_readonly flag is set, or when every
440
+ method (including inherited ones) is individually @readonly.
441
+ """
442
+ pi = self.ctx.analyzer.registry.scan_by_short_name(protocol_name)
443
+ if pi is None:
444
+ return False
445
+ if pi.is_readonly:
446
+ return True
447
+ all_methods = self.collect_concept_methods(protocol_name)
448
+ return bool(all_methods) and all(m.is_readonly for m in all_methods)
449
+
450
+ def collect_concept_fields(self, protocol_name: str) -> list[tuple[str, TpyType]]:
451
+ return self.ctx.analyzer.protocols.collect_protocol_fields(protocol_name)
452
+
453
+ def gen_concept_decl(self, out: TextIO, protocol: TpyProtocol) -> None:
454
+ """Generate a C++20 concept for a user-defined protocol.
455
+
456
+ SelfType in method signatures is rendered as 'T' (the template parameter).
457
+ This allows the concept to check that e.g., T + T -> T.
458
+
459
+ For generic protocols like Container[T], we generate:
460
+ - template<typename T, typename _T0> where T is the checked type and _T0 is the protocol's T
461
+ - Usage: Container<int32_t> V means V must satisfy Container<V, int32_t>
462
+
463
+ For protocol inheritance, includes requirements from all parent protocols.
464
+
465
+ Protocols with cpp_concept (e.g. @native marker protocols) are backed by
466
+ runtime C++ concepts and don't need generated concept definitions.
467
+
468
+ Returns True if a concept was emitted, False if skipped.
469
+ """
470
+ # Skip protocols backed by runtime C++ concepts
471
+ protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
472
+ if protocol_info and protocol_info.cpp_concept:
473
+ return False
474
+ # For @dynamic protocols, forward-declare the abstract base struct so
475
+ # the concept can mention the protocol's own type in method signatures
476
+ # (e.g. `clone(self) -> Own[Self]` lowering to `std::unique_ptr<Proto>`
477
+ # inside a convertible_to constraint). The base struct is fully defined
478
+ # by gen_dynamic_base_class below; concept constraints only require
479
+ # the type to be complete at instantiation time.
480
+ if protocol.is_dynamic:
481
+ self.emit_dynamic_base_forward_decl(out, protocol)
482
+ self.ctx.emit_preceding_comments(out, protocol.loc)
483
+ self.ctx.emit_source_comment(out, protocol.loc)
484
+ # Build template params: T (checked type) + one for each protocol type param
485
+ template_params = ["typename T"]
486
+ type_param_map: dict[str, str] = {} # Protocol type param -> C++ template param
487
+ for i, tp in enumerate(protocol.type_params):
488
+ cpp_param = f"_T{i}"
489
+ template_params.append(f"typename {cpp_param}")
490
+ type_param_map[tp] = cpp_param
491
+
492
+ out.write(f"template<{', '.join(template_params)}>\n")
493
+
494
+ # Collect all methods including inherited ones
495
+ all_methods = self.collect_concept_methods(protocol.name)
496
+ all_fields_for_decl = self.collect_concept_fields(protocol.name)
497
+
498
+ # @dynamic protocols use __{Name}_Concept__ so the clean name is
499
+ # free for the base class struct
500
+ concept_name = (f"__{protocol.name}_Concept__"
501
+ if protocol.is_dynamic else protocol.name)
502
+ # Marker (no methods, no fields) -> trivially-satisfied concept.
503
+ # An empty `requires(T& t) { }` body is ill-formed in C++20 (GCC/Clang
504
+ # reject it), so emit `= true` instead. Used by markerless @dynamic
505
+ # protocols that act as phylum tags (e.g. Throwable for the exception
506
+ # tree's polymorphism predicate).
507
+ if not all_methods and not all_fields_for_decl:
508
+ out.write(f"concept {concept_name} = true;\n")
509
+ return True
510
+ if self.is_protocol_const(protocol.name):
511
+ out.write(f"concept {concept_name} = requires(const T& t) {{\n")
512
+ else:
513
+ out.write(f"concept {concept_name} = requires(T& t) {{\n")
514
+
515
+
516
+ def subst_type(typ: TpyType) -> TpyType:
517
+ """Substitute protocol type params recursively in a type."""
518
+ if isinstance(typ, TypeParamRef) and typ.name in type_param_map:
519
+ # Return a TypeParamRef with the mapped name
520
+ return TypeParamRef(type_param_map[typ.name])
521
+ return typ.map_inner_types(subst_type)
522
+
523
+ def subst_to_cpp(typ: TpyType) -> str:
524
+ """Convert type to C++, substituting protocol type params."""
525
+ return subst_type(typ).to_cpp()
526
+
527
+ def concept_type_cpp(typ: TpyType) -> str:
528
+ """C++ type for concept constraint, with covariant str handling.
529
+
530
+ Protocol -> str uses string_view so implementations can return
531
+ any string type (str, StrView, String).
532
+ """
533
+ resolved = subst_type(typ)
534
+ if is_str_type(resolved):
535
+ return "std::string_view"
536
+ return resolved.to_cpp()
537
+
538
+ for method_sig in all_methods:
539
+ # Generate requirement for each method.
540
+ # SelfType.to_cpp() returns "T", so this handles Self -> T substitution.
541
+ # Protocol type params (e.g., T in Container[T]) are mapped to _T0, _T1, etc.
542
+ ret_cpp = concept_type_cpp(method_sig.return_type)
543
+ # Skip return type check when the C++ return doesn't match the
544
+ # protocol signature: protocol returns (Iterator[T] -- can't
545
+ # convertible_to a concept), __next__ (C++ returns
546
+ # std::expected<T, StopIteration>, not T), and Self on __iter__
547
+ # (just checks the expression is valid, not the return type).
548
+ skip_return_check = (is_protocol_type(method_sig.return_type)
549
+ or method_sig.name == "__next__"
550
+ or (isinstance(method_sig.return_type, SelfType)
551
+ and method_sig.name not in DUNDER_TO_BINARY_OP))
552
+
553
+ # Build the C++ call expression from DUNDER_CPP_TEMPLATES, binary
554
+ # operator map, or direct member call (in that priority order).
555
+ from ..modules import get_dunder_cpp_template
556
+ cpp_tmpl = get_dunder_cpp_template(method_sig.name)
557
+ if cpp_tmpl is not None:
558
+ # Expand template: {self} -> t, {0}/{1}/... -> std::declval<ParamCpp>()
559
+ call_expr = cpp_tmpl.replace("{self}", "t")
560
+ for i, (_, ptype) in enumerate(method_sig.params):
561
+ call_expr = call_expr.replace(f"{{{i}}}", f"std::declval<{subst_to_cpp(ptype)}>()")
562
+ elif method_sig.name in DUNDER_TO_BINARY_OP and len(method_sig.params) == 1:
563
+ cpp_op = DUNDER_TO_BINARY_OP[method_sig.name]
564
+ _, ptype = method_sig.params[0]
565
+ call_expr = f"t {cpp_op} std::declval<{subst_to_cpp(ptype)}>()"
566
+ else:
567
+ param_exprs = [f"std::declval<{subst_to_cpp(ptype)}>()" for _, ptype in method_sig.params]
568
+ call_expr = f"t.{method_sig.name}({', '.join(param_exprs)})"
569
+
570
+ if skip_return_check:
571
+ out.write(f"{INDENT}{call_expr};\n")
572
+ else:
573
+ out.write(f"{INDENT}{{ {call_expr} }} -> std::convertible_to<{ret_cpp}>;\n")
574
+
575
+ # Collect all fields including inherited ones
576
+ all_fields = self.collect_concept_fields(protocol.name)
577
+ for field_name, field_type in all_fields:
578
+ field_cpp = concept_type_cpp(field_type)
579
+ out.write(f"{INDENT}{{ t.{field_name} }} -> std::convertible_to<{field_cpp}>;\n")
580
+
581
+ out.write("};\n")
582
+ return True
583
+
584
+ def gen_dynamic_base_class(self, out: TextIO, protocol: TpyProtocol) -> None:
585
+ """Generate abstract base class for a @dynamic protocol.
586
+
587
+ Emitted inside the user namespace. The base class gets the protocol's
588
+ clean name (e.g., struct Pet) so C++ interop code can use it directly.
589
+ When this protocol extends other @dynamic protocols, the base class
590
+ inherits from their bases (e.g., NamedPet : Pet).
591
+
592
+ For generic @dynamic protocols (e.g. Awaitable[T]), the base is a
593
+ class template parameterized on the protocol's type params; method
594
+ signatures referring to those params render via TypeParamRef.to_cpp()
595
+ which already returns the bare name ("T").
596
+
597
+ Note on naming: the *concept* (emitted by gen_concept_decl) renames
598
+ protocol type params to `_T0`, `_T1`, ... because its first slot is
599
+ already taken by the checked type `T`. The base class here uses the
600
+ bare names directly. The two schemes round-trip via the C++20
601
+ abbreviated constraint form -- see gen_dynamic_adapter_specs for the
602
+ construction that bridges them.
603
+ """
604
+ protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
605
+ if protocol_info is None:
606
+ return
607
+
608
+ all_methods = self.collect_concept_methods(protocol.name)
609
+
610
+ # Collect @dynamic parent bases and their already-declared methods
611
+ dynamic_parent_bases: list[str] = []
612
+ parent_dynamic_methods: set[str] = set()
613
+ for parent in protocol_info.parent_protocols:
614
+ parent_info = self.ctx.analyzer.registry.scan_by_short_name(parent.name)
615
+ if parent_info and parent_info.is_dynamic:
616
+ dynamic_parent_bases.append(self.get_dynamic_base_name(parent))
617
+ for m in self.collect_concept_methods(parent.name):
618
+ parent_dynamic_methods.add(m.name)
619
+
620
+ # Methods to declare in this base class (exclude those in @dynamic parents)
621
+ base_methods = [m for m in all_methods if m.name not in parent_dynamic_methods]
622
+
623
+ if protocol.type_params:
624
+ out.write(self.gen_record_template_header(protocol.type_params, {}) + "\n")
625
+ base_name = protocol.name
626
+ if dynamic_parent_bases:
627
+ bases_str = ", ".join(dynamic_parent_bases)
628
+ out.write(f"struct {base_name} : {bases_str} {{\n")
629
+ else:
630
+ out.write(f"struct {base_name} {{\n")
631
+ for method_sig in base_methods:
632
+ ret_cpp = self._dynamic_return_type(method_sig)
633
+ const_qual = " const" if self._is_readonly_method(method_sig, protocol_info) else ""
634
+ params_cpp = self._dynamic_param_list(method_sig)
635
+ out.write(f"{INDENT}virtual {ret_cpp} {method_sig.name}({params_cpp}){const_qual} = 0;\n")
636
+ out.write(f"{INDENT}virtual ~{base_name}() = default;\n")
637
+ out.write("};\n")
638
+
639
+ @staticmethod
640
+ def emit_dynamic_base_forward_decl(out: TextIO, protocol: TpyProtocol) -> None:
641
+ """Write `[template<...>] struct {Proto};` for a @dynamic protocol.
642
+
643
+ Two call sites: the concept declaration in this module (so the concept
644
+ body can name the protocol's own base struct inside a `convertible_to`
645
+ constraint) and the cycle-peer `_fwd.hpp` header (so peer translation
646
+ units can refer to the base by name without including the full header).
647
+ """
648
+ cpp_name = protocol.name.replace(".", "::")
649
+ if protocol.type_params:
650
+ tparams = ", ".join(f"typename {tp}" for tp in protocol.type_params)
651
+ out.write(f"template<{tparams}> struct {cpp_name};\n")
652
+ else:
653
+ out.write(f"struct {cpp_name};\n")
654
+
655
+ def gen_dyn_protocol_base_trait(self, out: TextIO, protocol: TpyProtocol,
656
+ module_namespace: str) -> None:
657
+ """Generate the ``tpy::is_dyn_protocol_base<P>`` specialization only.
658
+
659
+ Split from ``gen_dynamic_adapter_specs`` so it can be emitted early
660
+ (right after the protocol forward decl, before any in-module use that
661
+ would implicitly instantiate the primary template via ``Box<P>``,
662
+ ``Adapter<P, T>``, etc.). The full Adapter / RefAdapter specs are
663
+ emitted later (see ``gen_dynamic_adapter_specs``) so their override
664
+ bodies see complete value types referenced in protocol method
665
+ signatures.
666
+ """
667
+ protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
668
+ if protocol_info is None:
669
+ return
670
+ if protocol_info.cpp_concept:
671
+ return
672
+ qbase_name = f"{module_namespace}::{protocol.name}"
673
+ if protocol.type_params:
674
+ tparam_refs = ", ".join(protocol.type_params)
675
+ qbase = f"{qbase_name}<{tparam_refs}>"
676
+ tparam_only = "template<" + ", ".join(f"typename {tp}" for tp in protocol.type_params) + ">"
677
+ out.write(f"{tparam_only}\n")
678
+ out.write(f"struct tpy::is_dyn_protocol_base<{qbase}> : std::true_type {{}};\n\n")
679
+ else:
680
+ out.write(f"template<> struct tpy::is_dyn_protocol_base<{qbase_name}> : std::true_type {{}};\n\n")
681
+
682
+ def gen_dynamic_adapter_specs(self, out: TextIO, protocol: TpyProtocol,
683
+ module_namespace: str,
684
+ user_type_names: set[str] | None = None) -> None:
685
+ """Generate ::tpy::Adapter and ::tpy::RefAdapter partial specializations.
686
+
687
+ Emitted at global scope (outside user namespace), following the EnumUtil
688
+ pattern. Uses fully-qualified names for concept and base class.
689
+
690
+ For generic @dynamic protocols, both adapters become class templates
691
+ parameterized on the protocol's type params + a concrete impl. The
692
+ concept constraint uses the C++20 abbreviated form `Concept<args>
693
+ Impl`, which desugars to `requires Concept<Impl, args>` -- matching
694
+ the concept signature `template<typename T_checked, typename _T0,
695
+ ...>` emitted by gen_concept_decl.
696
+
697
+ ``user_type_names`` is the set of record/protocol short names declared
698
+ in the current module; the adapter overrides qualify any bare
699
+ occurrence of these names with ``module_namespace::`` so they resolve
700
+ from inside the ``tpy::`` namespace (where the user namespace's
701
+ injected names are not visible, and for generic protocols the
702
+ dependent base class is not searched by unqualified lookup).
703
+
704
+ The ``tpy::is_dyn_protocol_base<P>`` trait specialization is emitted
705
+ separately by ``gen_dyn_protocol_base_trait`` (called earlier so
706
+ in-module uses of ``Box<P>`` resolve the trait correctly before the
707
+ Adapter spec lands).
708
+ """
709
+ protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
710
+ if protocol_info is None:
711
+ return
712
+ # @native + @dynamic: the runtime owns the abstract base and there
713
+ # is no codegen-emitted concept, so Adapter/RefAdapter specializations
714
+ # cannot be expressed here. Inheritance-only conformance covers the
715
+ # use case (Throwable: every concrete subclass inherits BaseException
716
+ # which inherits the runtime ::tpy::Throwable directly).
717
+ if protocol_info.cpp_concept:
718
+ return
719
+
720
+ all_methods = self.collect_concept_methods(protocol.name)
721
+ qbase_name = f"{module_namespace}::{protocol.name}"
722
+ qconcept = f"{module_namespace}::__{protocol.name}_Concept__"
723
+
724
+ if protocol.type_params:
725
+ impl = _ADAPTER_IMPL_GENERIC
726
+ if impl in protocol.type_params:
727
+ # Without this guard the redeclaration would surface only as a
728
+ # raw C++ error -- give the user a tpyc-level diagnostic.
729
+ raise CodeGenError(
730
+ f"@dynamic protocol '{protocol.name}' cannot declare a "
731
+ f"type parameter named '{impl}' (reserved by the "
732
+ f"@dynamic adapter codegen). Rename the type parameter "
733
+ f"(e.g. 'T').",
734
+ loc=protocol.loc,
735
+ )
736
+ tparam_decls = [f"typename {tp}" for tp in protocol.type_params]
737
+ tparam_refs = ", ".join(protocol.type_params)
738
+ tparam_decls.append(f"{qconcept}<{tparam_refs}> {impl}")
739
+ template_header = "template<" + ", ".join(tparam_decls) + ">"
740
+ qbase = f"{qbase_name}<{tparam_refs}>"
741
+ else:
742
+ impl = _ADAPTER_IMPL_NON_GENERIC
743
+ template_header = f"template<{qconcept} {impl}>"
744
+ qbase = qbase_name
745
+
746
+ # -- Owning adapter (for locals and rvalue call-site args) --
747
+ out.write(f"{template_header}\n")
748
+ out.write(f"struct tpy::Adapter<{qbase}, {impl}> : {qbase} {{\n")
749
+ out.write(f"{INDENT}{impl} inner;\n")
750
+ out.write(f"{INDENT}template<typename... Args>\n")
751
+ out.write(f"{INDENT}Adapter(Args&&... args) : inner(std::forward<Args>(args)...) {{}}\n")
752
+ self._gen_adapter_overrides(out, all_methods, protocol_info,
753
+ module_namespace, user_type_names)
754
+ out.write("};\n\n")
755
+
756
+ # -- Ref adapter (for lvalue call-site args, zero-copy) --
757
+ out.write(f"{template_header}\n")
758
+ out.write(f"struct tpy::RefAdapter<{qbase}, {impl}> : {qbase} {{\n")
759
+ out.write(f"{INDENT}{impl}& inner;\n")
760
+ out.write(f"{INDENT}RefAdapter({impl}& ref) : inner(ref) {{}}\n")
761
+ self._gen_adapter_overrides(out, all_methods, protocol_info,
762
+ module_namespace, user_type_names)
763
+ out.write("};\n")
764
+
765
+ def _gen_adapter_overrides(self, out: TextIO, all_methods: list[MethodSignature],
766
+ protocol_info: 'ProtocolInfo',
767
+ module_namespace: str = "",
768
+ user_type_names: set[str] | None = None) -> None:
769
+ """Emit method override bodies shared by owning and ref adapters."""
770
+ for method_sig in all_methods:
771
+ ret_cpp = self._dynamic_return_type(method_sig)
772
+ const_qual = " const" if self._is_readonly_method(method_sig, protocol_info) else ""
773
+ params_cpp = self._dynamic_param_list(method_sig)
774
+ is_void = isinstance(method_sig.return_type, VoidType)
775
+ ret_kw = "" if is_void else "return "
776
+ call_expr = self._dynamic_forward_call(method_sig)
777
+ # Covariant str: inner may return string_view but vtable returns string;
778
+ # explicit construction handles the conversion.
779
+ if is_str_type(method_sig.return_type):
780
+ call_expr = f"std::string({call_expr})"
781
+ if user_type_names and module_namespace:
782
+ ret_cpp = self._qualify_user_types(ret_cpp, module_namespace, user_type_names)
783
+ params_cpp = self._qualify_user_types(params_cpp, module_namespace, user_type_names)
784
+ out.write(f"{INDENT}{ret_cpp} {method_sig.name}({params_cpp}){const_qual} override {{ {ret_kw}{call_expr}; }}\n")
785
+
786
+ @staticmethod
787
+ def _qualify_user_types(cpp_str: str, module_namespace: str,
788
+ user_type_names: set[str]) -> str:
789
+ """Prefix bare occurrences of ``user_type_names`` with ``module_namespace::``.
790
+
791
+ Used for adapter override return/parameter types, which are rendered
792
+ inside ``tpy::`` and cannot rely on either the user namespace's scope
793
+ or (for generic protocols) the dependent base class's injected names.
794
+ The lookbehind skips names that already follow ``::`` or another
795
+ identifier character, leaving fully qualified or substring matches
796
+ untouched.
797
+ """
798
+ import re
799
+ for name in user_type_names:
800
+ cpp_str = re.sub(
801
+ rf'(?<![:\w]){re.escape(name)}(?!\w)',
802
+ f'{module_namespace}::{name}',
803
+ cpp_str,
804
+ )
805
+ return cpp_str
806
+
807
+ def _is_readonly_method(self, method_sig: MethodSignature, protocol_info: 'ProtocolInfo') -> bool:
808
+ from ..typesys import ProtocolInfo as _PI
809
+ return method_sig.is_readonly or protocol_info.is_readonly
810
+
811
+ def _dynamic_return_type(self, method_sig: MethodSignature) -> str:
812
+ """C++ return type for a dynamic dispatch method."""
813
+ if isinstance(method_sig.return_type, VoidType):
814
+ return "void"
815
+ return method_sig.return_type.to_cpp()
816
+
817
+ def _dynamic_param_list(self, method_sig: MethodSignature) -> str:
818
+ """C++ parameter list for a dynamic dispatch method (excluding self)."""
819
+ parts = []
820
+ for pname, ptype in method_sig.params:
821
+ parts.append(ptype.to_cpp_param(pname))
822
+ return ", ".join(parts)
823
+
824
+ def _dynamic_forward_call(self, method_sig: MethodSignature) -> str:
825
+ """Generate the forwarding call expression for an adapter method."""
826
+ arg_names = [pname for pname, _ in method_sig.params]
827
+ args_str = ", ".join(arg_names)
828
+
829
+ # Dunder methods with ::tpy:: free function equivalents
830
+ if method_sig.name == "__len__":
831
+ return f"::tpy::__len__(inner)"
832
+ if method_sig.name == "__getitem__" and len(method_sig.params) == 1:
833
+ return f"::tpy::__getitem__(inner, {args_str})"
834
+ if method_sig.name == "__setitem__" and len(method_sig.params) == 2:
835
+ return f"::tpy::__setitem__(inner, {args_str})"
836
+ if method_sig.name in DUNDER_TO_BINARY_OP and len(method_sig.params) == 1:
837
+ cpp_op = DUNDER_TO_BINARY_OP[method_sig.name]
838
+ return f"inner {cpp_op} {arg_names[0]}"
839
+
840
+ return f"inner.{method_sig.name}({args_str})"
841
+
842
+ def collect_record_types_from_type(self, typ: TpyType, result: set[str]) -> None:
843
+ """Recursively collect all record type names from a type.
844
+
845
+ Codegen iterates `TpyProtocol.methods` (parser AST, not rewritten by
846
+ sema's `register_protocol`), so some NominalTypes here are still bare
847
+ parser placeholders (no `_module_qname`, no TypeDef entry). For those,
848
+ `is_user_record` is False; fall back to the record registry directly.
849
+
850
+ `@builtin_type` records (e.g. `tpy.coro.Waker`) are included too: when
851
+ they're TPy-defined in the current module, the forward-decl is
852
+ required just like a plain user record. The emit step filters by
853
+ `module_record_names`, so off-module builtin entries are dropped
854
+ there harmlessly.
855
+ """
856
+ if isinstance(typ, NominalType) and not typ.is_protocol:
857
+ if typ.is_user_record:
858
+ result.add(typ.name)
859
+ else:
860
+ info = self.ctx.analyzer.registry.get_record(typ.name)
861
+ if info is not None:
862
+ result.add(typ.name)
863
+ for inner in typ.inner_types():
864
+ self.collect_record_types_from_type(inner, result)
865
+
866
+ def collect_type_args_of_bounded_records(
867
+ self,
868
+ typ: TpyType,
869
+ records_by_name: dict[str, TpyRecord],
870
+ result: set[str]
871
+ ) -> None:
872
+ """Collect record names used as type args to records with user-defined bounds.
873
+
874
+ When a protocol references Container[Message] where Container has a user-defined
875
+ protocol bound, C++ needs Message to be fully defined to check the constraint.
876
+ Same placeholder-tolerance story as `collect_record_types_from_type` --
877
+ accept either a resolved user-record NominalType or a bare placeholder
878
+ whose name resolves to a non-builtin RecordInfo in the registry.
879
+ """
880
+ registry = self.ctx.analyzer.registry
881
+
882
+ def is_user_record_nominal(t: TpyType) -> bool:
883
+ if not isinstance(t, NominalType) or t.is_protocol:
884
+ return False
885
+ if t.is_user_record:
886
+ return True
887
+ info = registry.get_record(t.name)
888
+ return info is not None and info.builtin_type_key is None
889
+
890
+ if (isinstance(typ, NominalType) and typ.type_args
891
+ and is_user_record_nominal(typ)):
892
+ record = records_by_name.get(typ.name)
893
+ if record and record.type_param_bounds:
894
+ # Check if any bound is a user-defined protocol
895
+ has_user_bound = any(
896
+ (pi := protocol_info_of(bound)) is None or pi.cpp_concept is None
897
+ for bound in record.type_param_bounds.values()
898
+ )
899
+ if has_user_bound:
900
+ # Collect all type args as needing early definition
901
+ for type_arg in typ.type_args:
902
+ if isinstance(type_arg, NominalType) and is_user_record_nominal(type_arg):
903
+ result.add(type_arg.name)
904
+
905
+ # Recurse into inner types
906
+ for inner in typ.inner_types():
907
+ self.collect_type_args_of_bounded_records(inner, records_by_name, result)