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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
  2. tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
  3. tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
  4. tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
  5. tpyc/__init__.py +104 -0
  6. tpyc/__main__.py +6 -0
  7. tpyc/_buildinfo.py +1 -0
  8. tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
  9. tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
  10. tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
  11. tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
  12. tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
  13. tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
  14. tpyc/_data/lib/tpy/_functools_macros.py +80 -0
  15. tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
  16. tpyc/_data/lib/tpy/argparse.py +2062 -0
  17. tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
  18. tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
  19. tpyc/_data/lib/tpy/base64.py +410 -0
  20. tpyc/_data/lib/tpy/bisect.py +39 -0
  21. tpyc/_data/lib/tpy/builtins.py +38 -0
  22. tpyc/_data/lib/tpy/dataclasses.py +354 -0
  23. tpyc/_data/lib/tpy/enum.py +23 -0
  24. tpyc/_data/lib/tpy/functools.py +33 -0
  25. tpyc/_data/lib/tpy/hashlib.py +206 -0
  26. tpyc/_data/lib/tpy/heapq.py +118 -0
  27. tpyc/_data/lib/tpy/io.py +395 -0
  28. tpyc/_data/lib/tpy/json.py +221 -0
  29. tpyc/_data/lib/tpy/math.py +406 -0
  30. tpyc/_data/lib/tpy/random.py +597 -0
  31. tpyc/_data/lib/tpy/re.py +467 -0
  32. tpyc/_data/lib/tpy/socket.py +379 -0
  33. tpyc/_data/lib/tpy/struct.py +178 -0
  34. tpyc/_data/lib/tpy/sys.py +40 -0
  35. tpyc/_data/lib/tpy/time.py +39 -0
  36. tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
  37. tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
  38. tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
  39. tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
  40. tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
  41. tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
  42. tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
  43. tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
  44. tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
  45. tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
  46. tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
  47. tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
  48. tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
  49. tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
  50. tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
  51. tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
  52. tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
  53. tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
  54. tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
  55. tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
  56. tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
  57. tpyc/_data/lib/tpy/tpy/_version.py +29 -0
  58. tpyc/_data/lib/tpy/tpy/bits.py +28 -0
  59. tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
  60. tpyc/_data/lib/tpy/tpy/extern.py +8 -0
  61. tpyc/_data/lib/tpy/tpy/mem.py +49 -0
  62. tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
  63. tpyc/_data/lib/tpy/tpy/version.py +21 -0
  64. tpyc/_data/lib/tpy/typing.py +13 -0
  65. tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
  66. tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
  67. tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
  68. tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
  69. tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
  70. tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
  71. tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
  72. tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
  73. tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
  74. tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
  75. tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
  76. tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
  77. tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
  78. tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
  79. tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
  80. tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
  81. tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
  82. tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
  83. tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
  84. tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
  85. tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
  86. tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
  87. tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
  88. tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
  89. tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
  90. tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
  91. tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
  92. tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
  93. tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
  94. tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
  95. tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
  96. tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
  97. tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
  98. tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
  99. tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
  100. tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
  101. tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
  102. tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
  103. tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
  104. tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
  105. tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
  106. tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
  107. tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
  108. tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
  109. tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
  110. tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
  111. tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
  112. tpyc/_data/runtime/cpp/third_party/README.md +58 -0
  113. tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
  114. tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
  115. tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
  116. tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
  117. tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
  118. tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
  119. tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
  120. tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
  121. tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
  122. tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
  123. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
  124. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
  125. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
  126. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
  127. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
  128. tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
  129. tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
  130. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
  131. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
  132. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
  133. tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
  134. tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
  135. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
  136. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
  137. tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
  138. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
  139. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
  140. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
  141. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
  142. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
  143. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
  144. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
  145. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
  146. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
  147. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
  148. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
  149. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
  150. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
  151. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
  152. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
  153. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
  154. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
  155. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
  156. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
  157. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
  158. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
  159. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
  160. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
  161. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
  162. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
  163. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
  164. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
  165. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
  166. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
  167. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
  168. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
  169. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
  170. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
  171. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
  172. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
  173. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
  174. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
  175. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
  176. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
  177. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
  178. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
  179. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
  180. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
  181. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
  182. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
  183. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
  184. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
  185. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
  186. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
  187. tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
  188. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
  189. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
  190. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
  191. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
  192. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
  193. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
  194. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
  195. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
  196. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
  197. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
  198. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
  199. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
  200. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
  201. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
  202. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
  203. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
  204. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
  205. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
  206. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
  207. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
  208. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
  209. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
  210. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
  211. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
  212. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
  213. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
  214. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
  215. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
  216. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
  217. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
  218. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
  219. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
  220. tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
  221. tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
  222. tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
  223. tpyc/build/__init__.py +7 -0
  224. tpyc/build/pcre2.py +122 -0
  225. tpyc/build/third_party.py +413 -0
  226. tpyc/cli.py +822 -0
  227. tpyc/codegen_cpp/__init__.py +18 -0
  228. tpyc/codegen_cpp/builtins.py +484 -0
  229. tpyc/codegen_cpp/context.py +2064 -0
  230. tpyc/codegen_cpp/expressions.py +5940 -0
  231. tpyc/codegen_cpp/functions.py +1913 -0
  232. tpyc/codegen_cpp/gen_async.py +3258 -0
  233. tpyc/codegen_cpp/gen_generators.py +657 -0
  234. tpyc/codegen_cpp/generator.py +2258 -0
  235. tpyc/codegen_cpp/match.py +1997 -0
  236. tpyc/codegen_cpp/param_const.py +172 -0
  237. tpyc/codegen_cpp/protocols.py +907 -0
  238. tpyc/codegen_cpp/records.py +1654 -0
  239. tpyc/codegen_cpp/resumable_cfg.py +1651 -0
  240. tpyc/codegen_cpp/statements.py +4963 -0
  241. tpyc/codegen_cpp/string_dispatch.py +76 -0
  242. tpyc/codegen_cpp/test_context.py +46 -0
  243. tpyc/codegen_cpp/test_param_const.py +113 -0
  244. tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
  245. tpyc/codegen_cpp/type_resolution.py +53 -0
  246. tpyc/codegen_cpp/types.py +436 -0
  247. tpyc/codegen_cpp/variant_access.py +135 -0
  248. tpyc/coercions.py +749 -0
  249. tpyc/compilation_context.py +57 -0
  250. tpyc/compiler.py +3945 -0
  251. tpyc/cycle_detection.py +358 -0
  252. tpyc/diagnostics.py +135 -0
  253. tpyc/dump_types.py +353 -0
  254. tpyc/frontend_diagnostics.py +47 -0
  255. tpyc/frontend_ir/__init__.py +140 -0
  256. tpyc/frontend_ir/lower.py +1098 -0
  257. tpyc/frontend_ir/nodes.py +718 -0
  258. tpyc/frontend_ir/resolver_adapter.py +151 -0
  259. tpyc/frontend_plugin.py +209 -0
  260. tpyc/install_docs.py +81 -0
  261. tpyc/liveness.py +756 -0
  262. tpyc/macro_api.py +1724 -0
  263. tpyc/macro_loader.py +497 -0
  264. tpyc/module_names.py +64 -0
  265. tpyc/modules/__init__.py +31 -0
  266. tpyc/modules/defs.py +89 -0
  267. tpyc/modules/registry.py +36 -0
  268. tpyc/modules/resolver.py +192 -0
  269. tpyc/modules/type_resolution.py +629 -0
  270. tpyc/namespace.py +172 -0
  271. tpyc/parse/__init__.py +84 -0
  272. tpyc/parse/imports.py +490 -0
  273. tpyc/parse/nodes.py +1732 -0
  274. tpyc/parse/parser.py +4043 -0
  275. tpyc/parse/resolve_refs.py +466 -0
  276. tpyc/parse/type_resolver.py +1060 -0
  277. tpyc/prescan.py +254 -0
  278. tpyc/qnames.py +149 -0
  279. tpyc/repl.py +529 -0
  280. tpyc/repl_backends.py +848 -0
  281. tpyc/sema/__init__.py +21 -0
  282. tpyc/sema/analyzer.py +3625 -0
  283. tpyc/sema/bound_check.py +72 -0
  284. tpyc/sema/builder_trace.py +684 -0
  285. tpyc/sema/calls.py +5406 -0
  286. tpyc/sema/compatibility.py +2107 -0
  287. tpyc/sema/context.py +1243 -0
  288. tpyc/sema/expressions.py +3737 -0
  289. tpyc/sema/flow_facts.py +199 -0
  290. tpyc/sema/init_tracker.py +150 -0
  291. tpyc/sema/list_literals.py +69 -0
  292. tpyc/sema/literal_utils.py +27 -0
  293. tpyc/sema/local_deduction.py +1088 -0
  294. tpyc/sema/macros.py +179 -0
  295. tpyc/sema/match.py +1177 -0
  296. tpyc/sema/method_expansion.py +347 -0
  297. tpyc/sema/methods.py +2197 -0
  298. tpyc/sema/mutation_propagation.py +268 -0
  299. tpyc/sema/narrowing.py +857 -0
  300. tpyc/sema/numeric_lattice.py +160 -0
  301. tpyc/sema/operators.py +402 -0
  302. tpyc/sema/overloads.py +841 -0
  303. tpyc/sema/protocols.py +1209 -0
  304. tpyc/sema/reach_analysis.py +202 -0
  305. tpyc/sema/registration.py +3156 -0
  306. tpyc/sema/scope_tracker.py +193 -0
  307. tpyc/sema/statements.py +4426 -0
  308. tpyc/sema/type_ops.py +1879 -0
  309. tpyc/sema/value_range.py +181 -0
  310. tpyc/symbol_binding.py +259 -0
  311. tpyc/test_c3_mro.py +208 -0
  312. tpyc/test_cli_argv.py +52 -0
  313. tpyc/test_compiler.py +559 -0
  314. tpyc/test_contains_type_param.py +101 -0
  315. tpyc/test_cycle_detection.py +221 -0
  316. tpyc/test_dump_types.py +225 -0
  317. tpyc/test_install_docs.py +65 -0
  318. tpyc/test_local_cpp_form.py +135 -0
  319. tpyc/test_macro_loader.py +76 -0
  320. tpyc/test_method_expansion.py +254 -0
  321. tpyc/test_nominal_identity.py +182 -0
  322. tpyc/test_overloads.py +410 -0
  323. tpyc/test_parse.py +303 -0
  324. tpyc/test_parse_type_ref.py +506 -0
  325. tpyc/test_parse_version_info.py +58 -0
  326. tpyc/test_reach_analysis.py +72 -0
  327. tpyc/test_ref_type.py +216 -0
  328. tpyc/test_send_sync_substitution.py +276 -0
  329. tpyc/test_tuple_mutation_propagation.py +206 -0
  330. tpyc/test_type_def_registry.py +1729 -0
  331. tpyc/test_union_types.py +195 -0
  332. tpyc/type_def_registry.py +975 -0
  333. tpyc/typesys.py +5104 -0
tpyc/sema/protocols.py ADDED
@@ -0,0 +1,1209 @@
1
+ """
2
+ TurboPython Protocol Checking
3
+
4
+ Protocol conformance checking and method/field lookups.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from enum import Enum
9
+ from typing import TYPE_CHECKING
10
+
11
+ import re
12
+
13
+ from ..typesys import (
14
+ TpyType, NominalType, TypeParamRef, SelfType, OwnType, ReadonlyType, RefType,
15
+ MethodSignature, FunctionInfo, FieldInfo, RecordInfo, PropertyInfo, is_protocol_type,
16
+ ListRepeatType, GenExprType, make_list, TupleType, OptionalType, PtrType, IntLiteralType, FloatLiteralType, AnyType, PendingListType, UnknownElementType, BIGINT, FLOAT,
17
+ impl_proto_matches_name, get_protocol_qname, unwrap_qualifiers,
18
+ del_suppresses_default_ctor,
19
+ )
20
+ from ..coercions import is_protocol_safe_coercion, is_protocol_type_arg_widening
21
+
22
+ if TYPE_CHECKING:
23
+ from ..typesys import TypeRegistry, ProtocolInfo
24
+ from .context import SemanticContext
25
+ from .type_ops import TypeOperations
26
+
27
+ from tpyc import modules as builtin_modules
28
+ from ..modules import get_dunder_cpp_template
29
+ from .. import qnames
30
+ from ..type_def_registry import (
31
+ is_copy_iter, is_own_iter, is_set, is_dict, is_array, is_span, is_list,
32
+ is_fixed_int_type, is_big_int_type, is_char_type, is_str_category,
33
+ is_enum_type, protocol_info_of, is_subtype,
34
+ )
35
+ from ..typesys import is_numeric_type
36
+ from .bound_check import find_method_class_param_bound_violation
37
+
38
+
39
+ def _build_parent_protocol_subst(
40
+ parent: NominalType, parent_info: 'ProtocolInfo | None',
41
+ ) -> dict[str, TpyType]:
42
+ """Build a substitution map from a parent protocol's type parameters
43
+ to the type arguments supplied in the inheritance reference.
44
+
45
+ For `class Foo[U](Iterable[U])`, walking from Foo and recursing into
46
+ Iterable yields {Iterable.T -> U} so inherited methods render with U.
47
+ Returns an empty map when parent_info is unknown (forward ref) or the
48
+ parent is non-generic.
49
+ """
50
+ if parent_info is None or not parent_info.type_params or not parent.type_args:
51
+ return {}
52
+ subst: dict[str, TpyType] = {}
53
+ # INT-kind args (e.g. Array[T, N=8]) don't appear in protocol method
54
+ # signatures, so they're skipped here -- the substitution map is only
55
+ # consumed by `substitute_types`, which works on TpyType.
56
+ for name, arg in zip(parent_info.type_params, parent.type_args):
57
+ if isinstance(arg, TpyType):
58
+ subst[name] = arg
59
+ return subst
60
+
61
+
62
+ def _substitute_method_signature(
63
+ method: MethodSignature, subst: dict[str, TpyType], type_ops: 'TypeOperations',
64
+ ) -> MethodSignature:
65
+ """Apply `subst` to a method signature's params and return type.
66
+
67
+ Returns the original signature unchanged when subst is empty.
68
+ """
69
+ if not subst:
70
+ return method
71
+ return MethodSignature(
72
+ name=method.name,
73
+ params=[(n, type_ops.substitute_types(t, subst)) for n, t in method.params],
74
+ return_type=type_ops.substitute_types(method.return_type, subst),
75
+ is_readonly=method.is_readonly,
76
+ readonly_opt_out=method.readonly_opt_out,
77
+ cpp_template=method.cpp_template,
78
+ param_defaults=method.param_defaults,
79
+ )
80
+
81
+
82
+ def record_extends_any(actual: TpyType, protocol_name: str, registry: 'TypeRegistry') -> bool:
83
+ """Check if a type extends any variant of a protocol (ignoring type args).
84
+
85
+ Standalone utility for callers without ProtocolChecker access.
86
+ Checks both implemented_protocols (user records) and extends_protocols (builtins).
87
+ """
88
+ record_info = registry.get_record_for_type(actual)
89
+ if record_info is None:
90
+ return False
91
+ target_qname = get_protocol_qname(protocol_name)
92
+ for impl_proto in record_info.implemented_protocols:
93
+ if impl_proto_matches_name(impl_proto, protocol_name, target_qname):
94
+ return True
95
+ # Extends strings are unqualified (only set on builtin types)
96
+ for ext in record_info.extends_protocols:
97
+ match = re.match(r"(\w+)(?:\[.+\])?$", ext)
98
+ if match and match.group(1) == protocol_name:
99
+ return True
100
+ return False
101
+
102
+
103
+ class ProtocolConformanceKind(Enum):
104
+ """How a type conforms to a protocol.
105
+
106
+ EXPLICIT: declared ``extends`` / ``implemented_protocols``, protocol-to-protocol
107
+ inheritance, or a compiler-intrinsic short-circuit (GenExpr/CopyIter/OwnIter
108
+ satisfying Iterable, Enum satisfying Hashable/Comparable/Equatable,
109
+ ValueType, Default, etc.). All of these represent intentional conformance
110
+ rather than an accidental method-set match.
111
+
112
+ STRUCTURAL: conformance proved only by walking the protocol's required
113
+ methods/fields and checking that ``actual`` has a matching signature for
114
+ each. Used as overload-resolution specificity signal -- explicit beats
115
+ structural when both match.
116
+ """
117
+ EXPLICIT = "explicit"
118
+ STRUCTURAL = "structural"
119
+
120
+
121
+ class ProtocolChecker:
122
+ """Protocol conformance checking and method/field lookups."""
123
+
124
+ def __init__(self, ctx: SemanticContext, type_ops: TypeOperations):
125
+ self.ctx = ctx
126
+ self.type_ops = type_ops
127
+
128
+ def is_all_readonly(self, proto_info: 'ProtocolInfo') -> bool:
129
+ """Check if all methods (including inherited) are readonly."""
130
+ if proto_info.is_readonly:
131
+ return True
132
+ if not all(m.is_readonly for m in proto_info.methods):
133
+ return False
134
+ for parent in proto_info.parent_protocols:
135
+ parent_info = self.ctx.registry.scan_by_short_name(parent.name)
136
+ if parent_info is None:
137
+ return False # unknown parent -- conservatively non-readonly
138
+ if not self.is_all_readonly(parent_info):
139
+ return False
140
+ return True # all methods (if any) are readonly, all parents (if any) are readonly
141
+
142
+ def type_conforms_to_protocol(self, actual: TpyType, protocol: NominalType) -> bool:
143
+ """Check if actual type conforms to a protocol.
144
+
145
+ Thin bool wrapper over ``classify_protocol_conformance``; see there for
146
+ the two conformance mechanisms (explicit / structural) and how generic
147
+ protocols and Self are handled.
148
+ """
149
+ return self.classify_protocol_conformance(actual, protocol) is not None
150
+
151
+ def directly_implements_dynamic(self, concrete: TpyType, protocol: NominalType) -> bool:
152
+ """True iff `concrete` C++-inherits the @dynamic protocol base.
153
+
154
+ Mirrors codegen's same-named helper. Match is by short name through
155
+ the implemented_protocols chain; @native records always return False
156
+ (their C++ struct does NOT inherit the codegen-emitted base, even if
157
+ the TPy declaration claims to implement the protocol).
158
+
159
+ Called from the call-analysis path when deciding whether a structural
160
+ conformer of a @dynamic bound needs Adapter substitution at codegen
161
+ (see `TpyCall.representational_subst_params`).
162
+ """
163
+ if not isinstance(concrete, NominalType) or not concrete.is_user_record:
164
+ return False
165
+ record_info = self.ctx.registry.get_record(concrete.name)
166
+ if record_info is None or record_info.is_native:
167
+ return False
168
+ proto_name = protocol.name
169
+ for p in record_info.implemented_protocols:
170
+ pi = protocol_info_of(p)
171
+ if pi is None or not pi.is_dynamic:
172
+ continue
173
+ if p.name == proto_name or is_subtype(pi, proto_name):
174
+ return True
175
+ return False
176
+
177
+ def satisfies_bound(self, actual: TpyType, bound: TpyType) -> bool:
178
+ """Does `actual` satisfy a type-parameter `bound`?
179
+
180
+ A protocol bound (`T: Comparable`) uses structural conformance -- a
181
+ capability assertion. A class bound (`U: Animal`) or a type-param bound
182
+ (`U: T`) is a subtype assertion checked by nominal inheritance; that
183
+ subtyping is what backs the `Ptr[U] -> Ptr[Bound]` pointer upcast (see
184
+ CompatibilityChecker._is_representational_subtype).
185
+ """
186
+ if is_protocol_type(bound):
187
+ return self.type_conforms_to_protocol(actual, bound)
188
+ if isinstance(bound, NominalType) and bound.is_user_record:
189
+ return actual == bound or self.ctx.registry.is_subclass_of(actual, bound)
190
+ if isinstance(bound, TypeParamRef):
191
+ # Unsubstituted bound (a sibling/enclosing type param that wasn't
192
+ # resolved to a concrete type at this site). Identity is all that
193
+ # is provable here.
194
+ return actual == bound
195
+ # Builtin nominal bound (e.g. `U: T` substituted to `Int32`): identity
196
+ # is sufficient since builtins don't have a user-visible class hierarchy.
197
+ if actual == bound:
198
+ return True
199
+ return False
200
+
201
+ def classify_protocol_conformance(
202
+ self, actual: TpyType, protocol: NominalType,
203
+ ) -> ProtocolConformanceKind | None:
204
+ """Check if actual type conforms to a protocol and return how.
205
+
206
+ Two conformance mechanisms work in parallel:
207
+ 1. Explicit extends: type declares extends=["Protocol[T]"]
208
+ 2. Structural: type has all methods required by the protocol
209
+
210
+ For marker protocols (no methods/fields), only explicit extends works.
211
+ For protocols with methods, either mechanism suffices.
212
+
213
+ For generic protocols like Sequence[Int32]:
214
+ - Build type substitution map: {"T": Int32}
215
+ - Resolve each method signature with substitutions
216
+ - Check if actual type has the resolved methods
217
+
218
+ For Self type in protocols:
219
+ - Self is substituted with the actual type being checked
220
+ - e.g., checking Int32 against Addable with __add__(Self) -> Self
221
+ expects __add__(Int32) -> Int32
222
+
223
+ Returns EXPLICIT for compiler-intrinsic matches (e.g. Enum<->Hashable,
224
+ GenExpr<->Iterable, Tuple<->Hashable/Comparable/Equatable,
225
+ bounded-TypeParamRef shortcuts) and for declared ``extends`` /
226
+ protocol-to-protocol inheritance.
227
+ Returns STRUCTURAL only when the method/field walk at the tail is the
228
+ sole path that succeeded. Returns None when actual does not conform.
229
+ """
230
+ # Unwrap ownership/const/ref wrappers -- readonly[T], Own[T], Ref[T]
231
+ # conform to any protocol that T conforms to.
232
+ unwrapped = unwrap_qualifiers(actual)
233
+ if unwrapped is not actual:
234
+ return self.classify_protocol_conformance(unwrapped, protocol)
235
+
236
+ # IntLiteralType: check if default int type conforms
237
+ if isinstance(actual, IntLiteralType):
238
+ return self.classify_protocol_conformance(BIGINT, protocol)
239
+ # FloatLiteralType: check if float64 conforms
240
+ if isinstance(actual, FloatLiteralType):
241
+ return self.classify_protocol_conformance(FLOAT, protocol)
242
+
243
+ # PendingListType: delegate to list[T] (resolves to list or Array, both conform).
244
+ # Empty literal (UNKNOWN_ELEMENT) with a single-type-arg protocol trivially
245
+ # conforms -- no elements to check. The pending-list coercion slot needed
246
+ # to finalize the element type is written by the caller AFTER overload
247
+ # resolution picks a winner (see CallAnalyzer._maybe_coerce_empty_list_to_protocol
248
+ # in sema/calls.py); doing it here would cement the first-probed overload's
249
+ # element type even if that overload is later rejected.
250
+ if isinstance(actual, PendingListType):
251
+ if (isinstance(actual.element_type, UnknownElementType)
252
+ and protocol.type_args and len(protocol.type_args) == 1):
253
+ return ProtocolConformanceKind.EXPLICIT
254
+ return self.classify_protocol_conformance(make_list(actual.element_type), protocol)
255
+
256
+ # GenExprType: satisfies Iterable[T] and Iterator[T]
257
+ if isinstance(actual, GenExprType):
258
+ if protocol.qualified_name() in (qnames.ITERABLE, qnames.ITERATOR):
259
+ if protocol.type_args and len(protocol.type_args) == 1:
260
+ if self.type_ops.types_match_for_inference(actual.element_type, protocol.type_args[0]):
261
+ return ProtocolConformanceKind.EXPLICIT
262
+ return None
263
+ return ProtocolConformanceKind.EXPLICIT
264
+ return None
265
+
266
+ # CopyIter / OwnIter: satisfies Iterable[T] only (not Iterator).
267
+ # Both have C++ begin/end but are not exposed as Iterator to prevent
268
+ # passing to functions that call __next__() after a for-loop consumes them.
269
+ if is_copy_iter(actual) or is_own_iter(actual):
270
+ if protocol.qualified_name() == qnames.ITERABLE:
271
+ if protocol.type_args and len(protocol.type_args) == 1:
272
+ if self.type_ops.types_match_for_inference(actual.type_args[0], protocol.type_args[0]):
273
+ return ProtocolConformanceKind.EXPLICIT
274
+ return None
275
+ return ProtocolConformanceKind.EXPLICIT
276
+ return None
277
+
278
+ # Enum/IntEnum: hashable, comparable, and equatable at C++ level
279
+ if is_enum_type(actual):
280
+ if protocol.qualified_name() in (qnames.HASHABLE, qnames.COMPARABLE, qnames.EQUATABLE):
281
+ return ProtocolConformanceKind.EXPLICIT
282
+
283
+ # Any: satisfies Hashable / Equatable / Stringable / Representable at
284
+ # the type-system level. Runtime ops may panic if the contained T
285
+ # lacks the corresponding capability (see docs/ANY_TYPE_DESIGN.md);
286
+ # this is the trade-off for accepting arbitrary copyable contents.
287
+ # Iterable / Sized / Comparable / Sequence remain non-conforming;
288
+ # the user must narrow first.
289
+ if isinstance(actual, AnyType):
290
+ if protocol.qualified_name() in (
291
+ qnames.HASHABLE,
292
+ qnames.EQUATABLE,
293
+ qnames.STRINGABLE,
294
+ qnames.REPRESENTABLE,
295
+ ):
296
+ return ProtocolConformanceKind.EXPLICIT
297
+
298
+ # Tuple: element-wise compiler-intrinsic conformance for Hashable,
299
+ # Comparable, and Equatable. C++ std::tuple supplies the matching
300
+ # operators (hash via element hashers, lexicographic < and ==), so
301
+ # codegen needs no special path here -- the conformance check just
302
+ # has to reflect what the operators already support. Downgrade to
303
+ # STRUCTURAL if any element only structurally conforms, so the
304
+ # weakest link shows through in specificity ranking.
305
+ if isinstance(actual, TupleType):
306
+ if protocol.qualified_name() in (
307
+ qnames.HASHABLE, qnames.COMPARABLE, qnames.EQUATABLE,
308
+ ):
309
+ kind = ProtocolConformanceKind.EXPLICIT
310
+ for et in actual.element_types:
311
+ elem_kind = self.classify_protocol_conformance(et, protocol)
312
+ if elem_kind is None:
313
+ return None
314
+ if elem_kind is ProtocolConformanceKind.STRUCTURAL:
315
+ kind = ProtocolConformanceKind.STRUCTURAL
316
+ return kind
317
+
318
+ # Bounded type parameter: T: Sized conforms to Sized (and any protocol its bound conforms to)
319
+ if isinstance(actual, TypeParamRef):
320
+ if protocol.qualified_name() in (qnames.STRINGABLE, qnames.REPRESENTABLE):
321
+ return ProtocolConformanceKind.EXPLICIT
322
+ bound = self.type_ops.get_type_param_bound(actual.name)
323
+ if bound is not None and is_protocol_type(bound):
324
+ return self.classify_protocol_conformance(bound, protocol)
325
+ # No protocol-typed bound to delegate to. Stop here rather than
326
+ # fall through: the downstream marker arm runs `not predicate(T)`
327
+ # for markers like Copyable, where the predicate's conservative
328
+ # "no info -> False" returns silently flip to "conforms" through
329
+ # the negation. A type variable without an explicit bound can be
330
+ # instantiated with anything, so we cannot conclude conformance.
331
+ # User-visible consequence: shadow bounds (Box[T].clone[T: Copyable])
332
+ # called from inside an enclosing generic require the enclosing
333
+ # scope to propagate the bound (e.g. class C[T: Copyable] or
334
+ # def m[T: Copyable]).
335
+ return None
336
+
337
+ # Stringable: Python semantics dictate every type has __str__ via the
338
+ # object.__str__ default that falls back to __repr__. The C++ runtime
339
+ # (tpy::__str__ in runtime/cpp/include/tpy/dunder.hpp) implements this
340
+ # chain, so sema admits any type with either __str__ or __repr__ as
341
+ # conforming. This collapses the historical str.__init__(Stringable)
342
+ # vs str.__init__(Representable) overload pair into a single overload
343
+ # whose codegen template handles both branches via the runtime.
344
+ if protocol.qualified_name() == qnames.STRINGABLE:
345
+ record_info = self.ctx.registry.get_record_for_type(actual)
346
+ if record_info is not None:
347
+ if (record_info.get_method_overloads("__str__")
348
+ or record_info.get_method_overloads("__repr__")):
349
+ return ProtocolConformanceKind.EXPLICIT
350
+
351
+ # Unified lookup - all protocols (builtin and user) are in the registry
352
+ protocol_info = protocol_info_of(protocol)
353
+ if protocol_info is None:
354
+ return None
355
+
356
+ # Protocol-to-protocol: check if actual inherits from protocol (or is same protocol)
357
+ if is_protocol_type(actual):
358
+ if actual.name == protocol.name:
359
+ return (ProtocolConformanceKind.EXPLICIT
360
+ if actual.type_args == protocol.type_args else None)
361
+ # Check if actual protocol inherits from the required protocol
362
+ if self.protocol_inherits_from(actual.name, protocol.name):
363
+ return ProtocolConformanceKind.EXPLICIT
364
+
365
+ # Marker protocols require explicit extends declaration
366
+ if protocol_info.is_marker:
367
+ qname = protocol.qualified_name()
368
+ # ValueType: any type with value semantics conforms implicitly
369
+ if qname == qnames.VALUE_TYPE and actual.is_value_type():
370
+ return ProtocolConformanceKind.EXPLICIT
371
+ # Copyable: any type whose C++ representation is copy-constructible
372
+ # (i.e. not @nocopy, has no __del__, and -- transitively -- has no
373
+ # @nocopy field or parent). Mirrors what the runtime concept checks.
374
+ # Authoritative -- a user-declared `extends Copyable` on a record
375
+ # whose copy ctor is deleted by codegen (has_del / @nocopy / nested
376
+ # non-copyable) does not grant conformance, because the generated
377
+ # C++ would still reject any Copyable<T>-constrained use.
378
+ if qname == qnames.COPYABLE:
379
+ return (ProtocolConformanceKind.EXPLICIT
380
+ if not self.ctx.is_type_non_copyable(actual) else None)
381
+ # Default: types that support default construction. Authoritative --
382
+ # a user-declared `extends Default` on a record whose `__init__` has
383
+ # required parameters (or whose default ctor codegen suppresses --
384
+ # see `del_suppresses_default_ctor`) does not grant conformance,
385
+ # because the generated `T{}` would fail to compile. Mirrors the
386
+ # Copyable arm's same-shape fix.
387
+ if qname == qnames.DEFAULT:
388
+ return (ProtocolConformanceKind.EXPLICIT
389
+ if self._is_default_constructible(actual) else None)
390
+ return (ProtocolConformanceKind.EXPLICIT
391
+ if self._check_record_extends(actual, protocol) else None)
392
+
393
+ # For builtin types, extends_protocols strings are maintained by the compiler
394
+ # and are always consistent with the actual C++ implementation. Treat as
395
+ # authoritative to avoid re-checking methods we know exist.
396
+ if self._check_builtin_extends(actual, protocol):
397
+ return ProtocolConformanceKind.EXPLICIT
398
+
399
+ # Build type substitution map
400
+ type_subst: dict[str, TpyType] = {"Self": actual}
401
+ if protocol_info.type_params and protocol.type_args:
402
+ if len(protocol_info.type_params) != len(protocol.type_args):
403
+ return None
404
+ type_subst.update(dict(zip(protocol_info.type_params, protocol.type_args)))
405
+
406
+ # Collect all required methods (including inherited)
407
+ all_methods = self.collect_protocol_methods(protocol.name)
408
+ for method_sig in all_methods:
409
+ expected_params = [
410
+ self.type_ops.substitute_types(ptype, type_subst)
411
+ for _, ptype in method_sig.params
412
+ ]
413
+ expected_return = self.type_ops.substitute_types(method_sig.return_type, type_subst)
414
+ method_readonly = method_sig.is_readonly or protocol_info.is_readonly
415
+
416
+ if not self.type_has_method_with_signature(
417
+ actual, method_sig.name, expected_params, expected_return,
418
+ require_readonly=method_readonly,
419
+ ):
420
+ return None
421
+
422
+ # Collect all required fields (including inherited)
423
+ all_fields = self.collect_protocol_fields(protocol.name)
424
+ for field_name, field_type in all_fields:
425
+ expected_type = self.type_ops.substitute_types(field_type, type_subst)
426
+ if not self.type_has_field_with_type(actual, field_name, expected_type):
427
+ return None
428
+
429
+ return ProtocolConformanceKind.STRUCTURAL
430
+
431
+ def _check_record_extends(self, actual: TpyType, protocol: NominalType) -> bool:
432
+ """Check if a type extends a protocol via record-level declarations.
433
+
434
+ Unified check for both user records (implemented_protocols) and
435
+ builtin types (extends_protocols strings). Used for marker protocols
436
+ where explicit declaration is the only conformance path.
437
+ """
438
+ record_info = self.ctx.registry.get_record_for_type(actual)
439
+ if record_info is None:
440
+ return False
441
+
442
+ # User records: check implemented_protocols (concrete NominalTypes)
443
+ # Build substitution map for generic records (e.g., ArrayList[T, N] instantiated as ArrayList[Int32, 8])
444
+ type_subst: dict[str, TpyType] = {}
445
+ if record_info.type_params and isinstance(actual, NominalType) and actual.type_args:
446
+ for param_name, arg in zip(record_info.type_params, actual.type_args):
447
+ if isinstance(arg, TpyType): # skip int-kind params (e.g. N: int)
448
+ type_subst[param_name] = arg
449
+
450
+ target_qname = protocol.qualified_name() or get_protocol_qname(protocol.name)
451
+ for impl_proto in record_info.implemented_protocols:
452
+ if impl_proto_matches_name(impl_proto, protocol.name, target_qname):
453
+ if len(protocol.type_args) == 0:
454
+ return True
455
+ # Substitute type params in impl_proto's type_args before comparing
456
+ resolved_args = tuple(
457
+ self.type_ops.substitute_types(a, type_subst) if type_subst else a
458
+ for a in impl_proto.type_args
459
+ )
460
+ if resolved_args == protocol.type_args:
461
+ return True
462
+
463
+ # Builtin types: check extends_protocols strings
464
+ return self._match_extends_protocols(record_info, actual, protocol)
465
+
466
+ def _check_builtin_extends(self, actual: TpyType, protocol: NominalType) -> bool:
467
+ """Check extends_protocols for builtin types only (non-marker protocols)."""
468
+ record_info = self.ctx.registry.get_record_for_type(actual)
469
+ if record_info is None or not record_info.extends_protocols:
470
+ return False
471
+ return self._match_extends_protocols(record_info, actual, protocol)
472
+
473
+ def _match_extends_protocols(
474
+ self, record_info: RecordInfo, actual: TpyType, protocol: NominalType,
475
+ ) -> bool:
476
+ """Match extends_protocols strings against an expected protocol."""
477
+ if not record_info.extends_protocols:
478
+ return False
479
+ type_params = builtin_modules.extract_type_params(actual)
480
+ for ext in record_info.extends_protocols:
481
+ match = re.match(r"(\w+)\[(.+)\]$", ext)
482
+ if match:
483
+ ext_protocol = match.group(1)
484
+ ext_type_str = match.group(2)
485
+ if ext_protocol == protocol.name and len(protocol.type_args) == 1:
486
+ actual_type_arg = builtin_modules._resolve_extends_type_arg(ext_type_str, type_params)
487
+ if actual_type_arg is None:
488
+ continue
489
+ if actual_type_arg == protocol.type_args[0]:
490
+ return True
491
+ # Widening-only at the element level: list[Int16] satisfies
492
+ # Iterable[Int32], list[Int32] satisfies Iterable[BigInt],
493
+ # but list[Int32] does NOT satisfy Iterable[UInt8].
494
+ if is_protocol_type_arg_widening(
495
+ actual_type_arg, protocol.type_args[0], self.ctx.default_int_type,
496
+ ):
497
+ return True
498
+ elif ext == protocol.name and not protocol.type_args:
499
+ return True
500
+ return False
501
+
502
+ def _is_default_constructible(self, actual: TpyType) -> bool:
503
+ """Check if a type supports default construction (zero-arg init)."""
504
+ # All primitive value types are default-constructible
505
+ if (is_numeric_type(actual) or is_char_type(actual)
506
+ or is_str_category(actual)):
507
+ return True
508
+ # Empty containers are default-constructible
509
+ if is_list(actual) or is_span(actual) or is_dict(actual) or is_set(actual):
510
+ return True
511
+ # Optional[T] is default-constructible (std::nullopt)
512
+ if isinstance(actual, OptionalType):
513
+ return True
514
+ # Ptr[T] is a raw pointer (T*); default-constructible to nullptr.
515
+ if isinstance(actual, PtrType):
516
+ return True
517
+ # Array[T, N] is default-constructible if element T is
518
+ if is_array(actual):
519
+ elem = actual.get_element_type()
520
+ if elem is not None:
521
+ default_proto = NominalType("Default", (), is_protocol=True)
522
+ return self.type_conforms_to_protocol(elem, default_proto)
523
+ return False
524
+ # Tuple types: default-constructible if all element types are
525
+ if isinstance(actual, TupleType):
526
+ default_proto = NominalType("Default", (), is_protocol=True)
527
+ return all(
528
+ self.type_conforms_to_protocol(et, default_proto)
529
+ for et in actual.element_types
530
+ )
531
+ # User records: default-constructible if __init__ has no required params.
532
+ # Tolerate unresolved parser placeholders (make_default[Point]() type_args
533
+ # may not have flowed through resolve_type) by looking up the registry
534
+ # directly and filtering out @builtin_type stubs.
535
+ if isinstance(actual, NominalType) and not actual.is_protocol:
536
+ # Type-aware lookup: qname-first so cross-module parents (e.g. an
537
+ # imported `Child(Parent)` whose parent lives in another file)
538
+ # resolve correctly, with short-name fallback already built-in.
539
+ record = self.ctx.registry.get_record_for_type(actual)
540
+ if record is None or record.builtin_type_key:
541
+ return False
542
+ # Records whose default ctor codegen suppresses must not be reported
543
+ # as default-constructible here either, or sema accepts a call that
544
+ # the C++ build will silently delete. The shared predicate captures
545
+ # all three cases: @nocopy + __del__ (Box, Rc), __del__ + required-
546
+ # param __init__, __del__ + no __init__ + indeterminate-init field.
547
+ if del_suppresses_default_ctor(record):
548
+ return False
549
+ if not record.has_init:
550
+ # Aggregate records have an implicit field-wise C++ ctor;
551
+ # default-constructible iff every field type and parent is.
552
+ for p in record.parents:
553
+ if not self._is_default_constructible(p):
554
+ return False
555
+ for fld in record.fields:
556
+ if not self._is_default_constructible(fld.type):
557
+ return False
558
+ return True
559
+ required = sum(1 for _, _, default in record.init_params if default is None)
560
+ return required == 0
561
+ # TypeParamRef: only if it has an explicit Default bound (checked via
562
+ # the standard bound-propagation path in type_conforms_to_protocol)
563
+ return False
564
+
565
+ # Protocols that ListRepeatType conforms to (lazy repeat range)
566
+ _LIST_REPEAT_PROTOCOLS = {"Iterable", "Sized", "NativeIterable", "NativeRangeConstructible"}
567
+ _GENEXPR_PROTOCOLS = {"Iterable", "Iterator"}
568
+ _ITER_ADAPTER_PROTOCOLS = {"Iterable"}
569
+
570
+ def type_extends_any_protocol(self, actual: TpyType, protocol_name: str) -> bool:
571
+ """Check if a type extends any variant of a protocol (ignoring type args).
572
+
573
+ Unified check for both user records and builtin types.
574
+ Replaces builtin_modules.type_extends_any().
575
+ """
576
+ # ListRepeatType conforms to iteration/sizing protocols
577
+ if isinstance(actual, ListRepeatType):
578
+ return protocol_name in self._LIST_REPEAT_PROTOCOLS
579
+ if isinstance(actual, GenExprType):
580
+ return protocol_name in self._GENEXPR_PROTOCOLS
581
+ if is_copy_iter(actual) or is_own_iter(actual):
582
+ return protocol_name in self._ITER_ADAPTER_PROTOCOLS
583
+
584
+ record_info = self.ctx.registry.get_record_for_type(actual)
585
+ if record_info is None:
586
+ return False
587
+
588
+ # User records: check implemented_protocols
589
+ target_qname = get_protocol_qname(protocol_name)
590
+ for impl_proto in record_info.implemented_protocols:
591
+ if impl_proto_matches_name(impl_proto, protocol_name, target_qname):
592
+ return True
593
+
594
+ # Extends strings are unqualified (only set on builtin types)
595
+ for ext in record_info.extends_protocols:
596
+ match = re.match(r"(\w+)(?:\[.+\])?$", ext)
597
+ if match and match.group(1) == protocol_name:
598
+ return True
599
+
600
+ return False
601
+
602
+ def type_has_method_with_signature(
603
+ self,
604
+ actual: TpyType,
605
+ method_name: str,
606
+ expected_params: list[TpyType],
607
+ expected_return: TpyType,
608
+ require_readonly: bool = False,
609
+ ) -> bool:
610
+ """Check if a type has a method with the expected signature.
611
+
612
+ Works for user records (via RecordInfo), protocol types, and builtin types (via module system).
613
+
614
+ Note: For builtin types with generic methods (e.g., list.append(value: T)), the type
615
+ parameter comparison uses direct equality, which doesn't resolve type variables.
616
+ This is fine for Phase 1 protocols (only Sized with __len__() -> Int32), but would
617
+ need type parameter resolution for generic protocols like Iterable[T].
618
+ """
619
+ if is_protocol_type(actual):
620
+ protocol_info = protocol_info_of(actual)
621
+ if protocol_info is None:
622
+ return False
623
+
624
+ # Build type substitution map for generic protocols.
625
+ # Include Self so that methods returning Self (e.g. Iterator.__iter__)
626
+ # resolve to the concrete protocol type (e.g. Iterator[Int32]).
627
+ type_subst: dict[str, TpyType] = {"Self": actual}
628
+ if protocol_info.type_params and actual.type_args:
629
+ type_subst.update(dict(zip(protocol_info.type_params, actual.type_args)))
630
+
631
+ for method_sig in protocol_info.methods:
632
+ if method_sig.name == method_name:
633
+ if require_readonly and not (method_sig.is_readonly or protocol_info.is_readonly):
634
+ return False
635
+ # Resolve types with substitution
636
+ resolved_return = self.type_ops.substitute_types(method_sig.return_type, type_subst)
637
+ if not self._protocol_type_matches(resolved_return, expected_return):
638
+ return False
639
+ if len(method_sig.params) != len(expected_params):
640
+ return False
641
+ for (_, actual_ptype), expected_ptype in zip(method_sig.params, expected_params):
642
+ resolved_ptype = self.type_ops.substitute_types(actual_ptype, type_subst)
643
+ # Params use equality (not _protocol_type_matches which is
644
+ # covariant). Only unwrap Own/Readonly/Ref wrappers.
645
+ cmp_actual = resolved_ptype.wrapped if isinstance(resolved_ptype, (OwnType, ReadonlyType, RefType)) else resolved_ptype
646
+ cmp_expected = expected_ptype.wrapped if isinstance(expected_ptype, (OwnType, ReadonlyType, RefType)) else expected_ptype
647
+ if cmp_actual != cmp_expected:
648
+ return False
649
+ return True
650
+ return False
651
+ else:
652
+ # Record or builtin type - unified lookup via get_record_for_type
653
+ record_info = self.ctx.registry.get_record_for_type(actual)
654
+ if record_info is None:
655
+ return False
656
+
657
+ overloads, type_subst = self.lookup_record_method_overloads(record_info, method_name)
658
+ if not overloads:
659
+ return False
660
+
661
+ # Build instance-level type substitution
662
+ instance_subst = self.type_ops.build_type_substitution(actual)
663
+ if instance_subst:
664
+ if type_subst:
665
+ # Merge: resolve inherited params through instance params
666
+ combined = {**instance_subst}
667
+ for k, v in type_subst.items():
668
+ combined[k] = self.type_ops.substitute_type_params(v, instance_subst) if isinstance(v, TpyType) else v
669
+ type_subst = combined
670
+ else:
671
+ type_subst = instance_subst
672
+
673
+ # Transitively resolve TypeParamRef chains (e.g., Base.T->Mid.U->Child.V->Int32)
674
+ if type_subst:
675
+ changed = True
676
+ while changed:
677
+ changed = False
678
+ for k, v in type_subst.items():
679
+ if isinstance(v, TypeParamRef) and v.name in type_subst and type_subst[v.name] is not v:
680
+ type_subst[k] = type_subst[v.name]
681
+ changed = True
682
+
683
+ # Check if any overload matches the expected signature
684
+ for method in overloads:
685
+ resolved = self.type_ops.substitute_method_type_params(method, type_subst) if type_subst else method
686
+ # Check readonly requirement.
687
+ # Allow non-readonly methods when the return type has a protocol-safe
688
+ # coercion to the expected return (e.g. __span__() -> Span[T] satisfies
689
+ # a readonly protocol expecting Span[readonly[T]], since the compiler
690
+ # auto-generates a const overload).
691
+ if require_readonly and not resolved.is_readonly:
692
+ if not is_protocol_safe_coercion(resolved.return_type, expected_return):
693
+ continue
694
+ # Check return type (Own[T] in impl matches T in protocol)
695
+ if not self._protocol_type_matches(resolved.return_type, expected_return):
696
+ continue
697
+ # Check parameter count and types (account for defaults)
698
+ required_count = resolved.min_args
699
+ if not (required_count <= len(expected_params) <= len(resolved.params)):
700
+ continue
701
+ params_match = True
702
+ for (_, actual_ptype), expected_ptype in zip(resolved.params, expected_params):
703
+ if not self._protocol_type_matches(actual_ptype, expected_ptype):
704
+ params_match = False
705
+ break
706
+ if not params_match:
707
+ continue
708
+ # Class-shadowed method type-param bounds: `def m[T: Bound](...)`
709
+ # on `class C[T]` is "callable only when class T satisfies Bound",
710
+ # so Box[NotHashable] should NOT conform to Hashable just because
711
+ # Box has __hash__. type_subst has its TypeParamRef chains already
712
+ # resolved above, so multi-level inheritance sees the concrete T.
713
+ if type_subst and find_method_class_param_bound_violation(
714
+ method, record_info.type_params, type_subst,
715
+ self.type_conforms_to_protocol,
716
+ ) is not None:
717
+ continue
718
+ return True
719
+ return False
720
+
721
+ def type_has_field_with_type(self, actual: TpyType, field_name: str, expected_type: TpyType) -> bool:
722
+ """Check if a type has a field with the expected type."""
723
+ if isinstance(actual, NominalType) and actual.is_record:
724
+ record = self.ctx.registry.get_record_for_type(actual)
725
+ if record:
726
+ type_subst = self.type_ops.build_type_substitution(actual)
727
+ for fld in record.fields:
728
+ if fld.name == field_name:
729
+ field_type = fld.type
730
+ if type_subst:
731
+ field_type = self.type_ops.substitute_type_params(field_type, type_subst)
732
+ return field_type == expected_type
733
+ return False
734
+
735
+ def _protocol_type_matches(self, actual: TpyType, expected: TpyType) -> bool:
736
+ """Check if an actual method type matches the expected protocol type.
737
+
738
+ Exact match, Own[T] unwrapping, protocol-safe coercion (e.g. StrView -> str),
739
+ or conformance to a protocol return type.
740
+ """
741
+ if actual == expected:
742
+ return True
743
+ # SelfType in actual matches when expected is the record type (Self -> MyType)
744
+ if isinstance(actual, SelfType) and isinstance(expected, NominalType):
745
+ return True
746
+ # Generic Self: MyType[T] matches bare MyType (unparameterized Self substitution)
747
+ if (isinstance(actual, NominalType) and isinstance(expected, NominalType)
748
+ and actual.name == expected.name and not expected.type_args):
749
+ return True
750
+ # Unwrap ownership/const/ref wrappers: Own[T], readonly[T], and Ref[T]
751
+ # all satisfy protocol -> T. Ref[T] arises from make_ref on non-value
752
+ # return types; the reference is transparent for protocol conformance.
753
+ if isinstance(actual, (OwnType, ReadonlyType, RefType)):
754
+ unwrapped = actual.wrapped
755
+ else:
756
+ unwrapped = actual
757
+ if unwrapped != actual and unwrapped == expected:
758
+ return True
759
+ if is_protocol_safe_coercion(actual, expected):
760
+ return True
761
+ # Allow BigInt where a fixed int is expected (e.g. __len__() -> int
762
+ # satisfies Sized which expects -> Int32). The C++ side uses
763
+ # std::convertible_to<int32_t> so the implicit conversion is safe.
764
+ if is_fixed_int_type(expected) and is_big_int_type(unwrapped):
765
+ return True
766
+ # Inherited method return type: actual is a parent of expected.
767
+ # e.g. Counter.__iter__() -> Counter inherited by DoubleCounter,
768
+ # checked against Iterator[T] which expects __iter__() -> Self
769
+ # (= DoubleCounter). Counter is a valid supertype.
770
+ if self.ctx.registry.is_subclass_of(expected, unwrapped):
771
+ return True
772
+ # If expected is a protocol, check if actual conforms to it
773
+ if is_protocol_type(expected) and isinstance(expected, NominalType):
774
+ return self.type_conforms_to_protocol(unwrapped, expected)
775
+ return False
776
+
777
+ def collect_protocol_methods(self, protocol_name: str, visited: set[str] | None = None) -> list[MethodSignature]:
778
+ """Collect methods from a protocol and all its parents.
779
+
780
+ Avoids duplicates by name (direct methods take precedence over inherited).
781
+ Substitutes a parent's type parameters with the parent reference's
782
+ type arguments so a child of `Iterable[T]` sees `__iter__(self) ->
783
+ Iterator[T]` in the child's own type-param scope.
784
+ """
785
+ if visited is None:
786
+ visited = set()
787
+ if protocol_name in visited:
788
+ return [] # Prevent cycles
789
+ visited.add(protocol_name)
790
+
791
+ protocol_info = self.ctx.registry.scan_by_short_name(protocol_name)
792
+ if protocol_info is None:
793
+ return []
794
+
795
+ # Start with direct methods
796
+ methods_by_name: dict[str, MethodSignature] = {}
797
+ for method in protocol_info.methods:
798
+ methods_by_name[method.name] = method
799
+
800
+ # Add inherited methods (only if not already defined directly)
801
+ for parent in protocol_info.parent_protocols:
802
+ parent_info = self.ctx.registry.scan_by_short_name(parent.name)
803
+ type_subst = _build_parent_protocol_subst(parent, parent_info)
804
+ for method in self.collect_protocol_methods(parent.name, visited):
805
+ if method.name not in methods_by_name:
806
+ methods_by_name[method.name] = _substitute_method_signature(
807
+ method, type_subst, self.type_ops,
808
+ )
809
+
810
+ return list(methods_by_name.values())
811
+
812
+ def collect_protocol_fields(self, protocol_name: str, visited: set[str] | None = None) -> list[tuple[str, TpyType]]:
813
+ """Collect fields from a protocol and all its parents.
814
+
815
+ Avoids duplicates by name (direct fields take precedence over inherited).
816
+ Substitutes parent type parameters as in `collect_protocol_methods`.
817
+ """
818
+ if visited is None:
819
+ visited = set()
820
+ if protocol_name in visited:
821
+ return [] # Prevent cycles
822
+ visited.add(protocol_name)
823
+
824
+ protocol_info = self.ctx.registry.scan_by_short_name(protocol_name)
825
+ if protocol_info is None:
826
+ return []
827
+
828
+ # Start with direct fields
829
+ fields_by_name: dict[str, tuple[str, TpyType]] = {}
830
+ for field_name, field_type in protocol_info.fields:
831
+ fields_by_name[field_name] = (field_name, field_type)
832
+
833
+ # Add inherited fields (only if not already defined directly)
834
+ for parent in protocol_info.parent_protocols:
835
+ parent_info = self.ctx.registry.scan_by_short_name(parent.name)
836
+ type_subst = _build_parent_protocol_subst(parent, parent_info)
837
+ for field_name, field_type in self.collect_protocol_fields(parent.name, visited):
838
+ if field_name not in fields_by_name:
839
+ resolved_type = (self.type_ops.substitute_types(field_type, type_subst)
840
+ if type_subst else field_type)
841
+ fields_by_name[field_name] = (field_name, resolved_type)
842
+
843
+ return list(fields_by_name.values())
844
+
845
+ def protocol_inherits_from(self, protocol_name: str, ancestor_name: str) -> bool:
846
+ """Check if a protocol inherits from another protocol (directly or
847
+ indirectly), or is the same protocol.
848
+
849
+ Thin reflexive layer over the registry-level ``is_subtype`` kernel;
850
+ ``is_subtype`` itself is strict (ancestor-only), and conformance
851
+ callers want ``P inherits P`` to be true.
852
+ """
853
+ if protocol_name == ancestor_name:
854
+ return True
855
+ return is_subtype(
856
+ self.ctx.registry.scan_by_short_name(protocol_name), ancestor_name
857
+ )
858
+
859
+ def get_protocol_method_signature(
860
+ self,
861
+ protocol: NominalType,
862
+ method_name: str,
863
+ self_type: TpyType | None = None,
864
+ ) -> tuple[list[tuple[str, TpyType]], TpyType, str | None, list] | None:
865
+ """Get a method's signature from a protocol.
866
+
867
+ Returns (params, return_type, cpp_template, param_defaults) with Self
868
+ substituted, or None if not found. Searches the protocol and all its
869
+ parent protocols. `param_defaults` is the parallel default list (None
870
+ per required param, TpyExpr per optional param); empty when no defaults
871
+ were declared.
872
+
873
+ Args:
874
+ protocol: The protocol to look up the method in
875
+ method_name: Name of the method to find
876
+ self_type: Type to substitute for Self (defaults to protocol if None).
877
+ For bounded type params like T: Sized, pass T so that
878
+ Self in signatures resolves to T, not the protocol.
879
+ """
880
+ # Default Self to the protocol type if not specified
881
+ actual_self = self_type if self_type is not None else protocol
882
+
883
+ protocol_info = protocol_info_of(protocol)
884
+ if protocol_info is None:
885
+ return None
886
+
887
+ # Find the method (including inherited methods)
888
+ all_methods = self.collect_protocol_methods(protocol.name)
889
+ for method_sig in all_methods:
890
+ if method_sig.name == method_name:
891
+ # Build type substitution: Self -> actual_self, plus protocol type params
892
+ type_subst: dict[str, TpyType] = {"Self": actual_self}
893
+ if protocol_info.type_params and protocol.type_args:
894
+ type_subst.update(dict(zip(protocol_info.type_params, protocol.type_args)))
895
+
896
+ # Substitute Self and type params in params and return type
897
+ params = [
898
+ (pname, self.type_ops.substitute_types(ptype, type_subst))
899
+ for pname, ptype in method_sig.params
900
+ ]
901
+ return_type = self.type_ops.substitute_types(method_sig.return_type, type_subst)
902
+ cpp_template = method_sig.cpp_template
903
+ # .py protocols don't have cpp templates; use dunder map
904
+ if cpp_template is None:
905
+ cpp_template = get_dunder_cpp_template(method_name)
906
+ return (params, return_type, cpp_template, method_sig.param_defaults)
907
+
908
+ return None
909
+
910
+ def lookup_protocol_method_return(
911
+ self,
912
+ protocol: NominalType,
913
+ method_name: str,
914
+ arg_types: list[TpyType],
915
+ ) -> TpyType | None:
916
+ """Look up a method's return type in a protocol, checking argument compatibility.
917
+
918
+ For protocol-typed values, we use the protocol's method signatures.
919
+ Self in the protocol is bound to the protocol itself (not a concrete type).
920
+
921
+ Returns the method's return type if found and args match, None otherwise.
922
+ """
923
+ protocol_info = protocol_info_of(protocol)
924
+ if protocol_info is None:
925
+ return None
926
+
927
+ # Build type substitution: Self -> the protocol type itself, plus type params
928
+ type_subst: dict[str, TpyType] = {"Self": protocol}
929
+ if protocol_info.type_params and protocol.type_args:
930
+ type_subst.update(dict(zip(protocol_info.type_params, protocol.type_args)))
931
+
932
+ for method_sig in protocol_info.methods:
933
+ if method_sig.name == method_name:
934
+ # Check argument count
935
+ if len(method_sig.params) != len(arg_types):
936
+ return None
937
+
938
+ # Check argument types (recursively substituting Self and type params)
939
+ for (_, ptype), arg_type in zip(method_sig.params, arg_types):
940
+ expected_type = self.type_ops.substitute_types(ptype, type_subst)
941
+ if expected_type != arg_type:
942
+ return None
943
+
944
+ # Return type (recursively substituting Self and type params)
945
+ return self.type_ops.substitute_types(method_sig.return_type, type_subst)
946
+
947
+ return None
948
+
949
+ def lookup_record_field(self, record_info: RecordInfo, field_name: str) -> FieldInfo | None:
950
+ """Look up a field in a record, including inherited fields.
951
+
952
+ For generic parent classes, substitutes type parameters with concrete types.
953
+ E.g., if Container[T] has field `value: T` and IntContainer extends Container[Int32],
954
+ looking up `value` on IntContainer returns FieldInfo with type Int32.
955
+ """
956
+ # Check this record's own fields first
957
+ for fld in record_info.fields:
958
+ if fld.name == field_name:
959
+ return fld
960
+ # Walk each direct parent's chain (source / MRO order; diamond-free invariant
961
+ # guarantees no ancestor contributes twice).
962
+ for parent_type in record_info.parents:
963
+ parent_info = self.ctx.registry.get_record_for_type(parent_type)
964
+ if parent_info is None:
965
+ continue
966
+ inherited = self.lookup_record_field(parent_info, field_name)
967
+ if inherited:
968
+ type_subst = self.get_parent_type_subst(parent_type, parent_info)
969
+ if type_subst:
970
+ substituted_type = self.type_ops.substitute_type_params(inherited.type, type_subst)
971
+ return FieldInfo(
972
+ name=inherited.name,
973
+ type=substituted_type,
974
+ default_value=inherited.default_value
975
+ )
976
+ return inherited
977
+ return None
978
+
979
+ def find_field_parent_branches(
980
+ self, record_info: RecordInfo, field_name: str
981
+ ) -> list[str]:
982
+ """Return the names of direct parents whose MRO reaches `field_name`.
983
+
984
+ Used to detect same-name ambiguity across multi-base inheritance: when
985
+ the child doesn't declare the field itself and more than one direct
986
+ parent's MRO contributes it, unqualified access is ambiguous and the
987
+ user must disambiguate via `BaseN.field`.
988
+ """
989
+ result: list[str] = []
990
+ for parent_type in record_info.parents:
991
+ parent_info = self.ctx.registry.get_record_for_type(parent_type)
992
+ if parent_info is None:
993
+ continue
994
+ if self.lookup_record_field(parent_info, field_name) is not None:
995
+ result.append(parent_info.name)
996
+ return result
997
+
998
+ def find_class_constant_parent_branches(
999
+ self, record_info: RecordInfo, field_name: str
1000
+ ) -> list[str]:
1001
+ """Class-constant counterpart to `find_field_parent_branches`."""
1002
+ result: list[str] = []
1003
+ for parent_type in record_info.parents:
1004
+ parent_info = self.ctx.registry.get_record_for_type(parent_type)
1005
+ if parent_info is None:
1006
+ continue
1007
+ if self.ctx.registry.find_class_constant_owner(parent_info, field_name) is not None:
1008
+ result.append(parent_info.name)
1009
+ return result
1010
+
1011
+ def lookup_record_method(self, record_info: RecordInfo, method_name: str) -> FunctionInfo | None:
1012
+ """Look up a method in a record, including inherited methods.
1013
+
1014
+ For generic parent classes, substitutes type parameters with concrete types.
1015
+ E.g., if Container[T] has method `get() -> T` and IntContainer extends Container[Int32],
1016
+ looking up `get` on IntContainer returns FunctionInfo with return type Int32.
1017
+
1018
+ Supports inheritance from both user-defined classes and builtin types.
1019
+ Delegates to lookup_record_method_overloads and returns the first overload.
1020
+ """
1021
+ overloads, type_subst = self.lookup_record_method_overloads(record_info, method_name)
1022
+ if not overloads:
1023
+ return None
1024
+ method = overloads[0]
1025
+ return self.type_ops.substitute_method_type_params(method, type_subst) if type_subst else method
1026
+
1027
+ def lookup_record_property(self, record_info: RecordInfo, prop_name: str) -> 'PropertyInfo | None':
1028
+ """Look up a property by name, including inherited properties."""
1029
+ prop = record_info.properties.get(prop_name)
1030
+ if prop:
1031
+ return prop
1032
+ for parent_type in record_info.parents:
1033
+ parent_info = self.ctx.registry.get_record_for_type(parent_type)
1034
+ if parent_info is None:
1035
+ continue
1036
+ inherited = self.lookup_record_property(parent_info, prop_name)
1037
+ if inherited:
1038
+ return inherited
1039
+ return None
1040
+
1041
+ def lookup_record_method_overloads(
1042
+ self, record_info: RecordInfo, method_name: str
1043
+ ) -> tuple[list[FunctionInfo], dict[str, TpyType | int]]:
1044
+ """Look up all overloads for a method in a record, including inherited methods.
1045
+
1046
+ Returns (overloads, type_subst) where type_subst should be applied to
1047
+ substitute parent type parameters with concrete types.
1048
+
1049
+ For inherited methods from builtin types with multiple overloads (like pop()),
1050
+ this returns all overloads so proper overload resolution can be done.
1051
+ """
1052
+ # Check this record's own methods first
1053
+ overloads = record_info.get_method_overloads(method_name)
1054
+ if overloads:
1055
+ return (overloads, {})
1056
+
1057
+ # Check parents (user-defined or builtin) -- unified recursive path over each base.
1058
+ # Multi-base conflict-check in registration ensures we don't silently pick between
1059
+ # two conflicting definitions here, so first-match-wins is safe.
1060
+ for parent_type in record_info.parents:
1061
+ parent_info = self.ctx.registry.get_record_for_type(parent_type)
1062
+ if parent_info is None:
1063
+ continue
1064
+ inherited, parent_subst = self.lookup_record_method_overloads(parent_info, method_name)
1065
+ if inherited:
1066
+ type_subst = self.get_parent_type_subst(parent_type, parent_info)
1067
+ combined_subst = {**parent_subst, **type_subst}
1068
+ return (inherited, combined_subst)
1069
+
1070
+ return ([], {})
1071
+
1072
+ def get_parent_type_subst(
1073
+ self, parent_type: TpyType, parent_info: RecordInfo
1074
+ ) -> dict[str, TpyType | int]:
1075
+ """Build substitution map from parent's type parameters to concrete type args.
1076
+
1077
+ For NominalType parents (user or module), extracts type args directly.
1078
+ For non-NominalType parents (etc.), uses extract_type_params.
1079
+ """
1080
+ if not parent_info.type_params:
1081
+ return {}
1082
+
1083
+ if isinstance(parent_type, NominalType):
1084
+ if not parent_type.type_args:
1085
+ return {}
1086
+ return dict(zip(parent_info.type_params, parent_type.type_args))
1087
+ return builtin_modules.extract_type_params(parent_type)
1088
+
1089
+ def resolve_ancestor_instantiation(
1090
+ self, current_rec: RecordInfo, ancestor_info: RecordInfo,
1091
+ ) -> tuple[TpyType, dict[str, TpyType | int]]:
1092
+ """Find the concrete NominalType for `ancestor_info` on current_rec's
1093
+ MRO, plus its type-param substitution.
1094
+
1095
+ Handles generic ancestors where the same `ancestor_info` may appear
1096
+ with different type args on the MRO. Falls back to an unparameterized
1097
+ nominal when no instantiation carries the ancestor (simple non-generic
1098
+ inheritance).
1099
+ """
1100
+ parent_type: TpyType | None = None
1101
+ for anc_type in current_rec.mro_ancestors:
1102
+ if isinstance(anc_type, NominalType):
1103
+ anc_info = self.ctx.registry.get_record_for_type(anc_type)
1104
+ if anc_info is ancestor_info:
1105
+ parent_type = anc_type
1106
+ break
1107
+ if parent_type is None:
1108
+ parent_type = NominalType(
1109
+ ancestor_info.name,
1110
+ _module_qname=ancestor_info.qualified_name(),
1111
+ )
1112
+ return parent_type, self.get_parent_type_subst(parent_type, ancestor_info)
1113
+
1114
+ def get_protocol_conformance_issues(self, record_type: NominalType, protocol: NominalType) -> list[str]:
1115
+ """Get human-readable list of conformance issues for a record against a protocol.
1116
+
1117
+ Distinguishes genuinely-absent methods from methods that exist with a
1118
+ wrong signature (wrong return type, wrong params, or missing @readonly).
1119
+ Each entry is a short phrase describing one reason the record does not
1120
+ conform.
1121
+ """
1122
+ record_info = self.ctx.registry.get_record_for_type(record_type)
1123
+ if record_info is None:
1124
+ return []
1125
+
1126
+ protocol_info = protocol_info_of(protocol)
1127
+ if protocol_info is None:
1128
+ return []
1129
+
1130
+ issues: list[str] = []
1131
+ # Build type substitution map
1132
+ type_subst: dict[str, TpyType] = {"Self": record_type}
1133
+ if protocol_info.type_params and protocol.type_args:
1134
+ type_subst.update(dict(zip(protocol_info.type_params, protocol.type_args)))
1135
+
1136
+ # Collect all required methods (including inherited)
1137
+ all_methods = self.collect_protocol_methods(protocol.name)
1138
+ for method_sig in all_methods:
1139
+ expected_params = [
1140
+ self.type_ops.substitute_types(ptype, type_subst)
1141
+ for _, ptype in method_sig.params
1142
+ ]
1143
+ expected_return = self.type_ops.substitute_types(method_sig.return_type, type_subst)
1144
+ method_readonly = method_sig.is_readonly or protocol_info.is_readonly
1145
+
1146
+ if self.type_has_method_with_signature(
1147
+ record_type, method_sig.name, expected_params, expected_return,
1148
+ require_readonly=method_readonly,
1149
+ ):
1150
+ continue
1151
+ issues.append(self._describe_method_mismatch(
1152
+ record_type, method_sig.name, expected_params, expected_return,
1153
+ method_readonly,
1154
+ ))
1155
+
1156
+ # Check fields
1157
+ all_fields = self.collect_protocol_fields(protocol.name)
1158
+ for field_name, field_type in all_fields:
1159
+ expected_type = self.type_ops.substitute_types(field_type, type_subst)
1160
+ if not self.type_has_field_with_type(record_type, field_name, expected_type):
1161
+ issues.append(f"field '{field_name}: {expected_type}' missing or has wrong type")
1162
+
1163
+ return issues
1164
+
1165
+ def _describe_method_mismatch(
1166
+ self, actual: TpyType, method_name: str,
1167
+ expected_params: list[TpyType], expected_return: TpyType,
1168
+ require_readonly: bool,
1169
+ ) -> str:
1170
+ """Explain why type_has_method_with_signature returned False for one method."""
1171
+ expected_sig = self._format_method_signature(
1172
+ method_name, expected_params, expected_return, require_readonly)
1173
+
1174
+ record_info = self.ctx.registry.get_record_for_type(actual)
1175
+ if record_info is None:
1176
+ return f"missing method '{expected_sig}'"
1177
+ overloads, inherited_subst = self.lookup_record_method_overloads(record_info, method_name)
1178
+ if not overloads:
1179
+ return f"missing method '{expected_sig}'"
1180
+
1181
+ # Apply inherited-method substitution so that overloads inherited
1182
+ # from a generic parent (e.g. `Container[Int32]` giving a method
1183
+ # returning `T`) render the concrete `Int32` rather than the raw
1184
+ # `T` placeholder in the diagnostic.
1185
+ def _render(m: 'FunctionInfo') -> str:
1186
+ params = [
1187
+ self.type_ops.substitute_types(p.type, inherited_subst)
1188
+ if inherited_subst else p.type
1189
+ for p in m.params
1190
+ ]
1191
+ ret = (self.type_ops.substitute_types(m.return_type, inherited_subst)
1192
+ if inherited_subst else m.return_type)
1193
+ return self._format_method_signature(method_name, params, ret, m.is_readonly)
1194
+
1195
+ actual_sigs = [_render(m) for m in overloads]
1196
+ actual_desc = " / ".join(f"'{s}'" for s in actual_sigs)
1197
+ return (
1198
+ f"method '{method_name}' has wrong signature: expected '{expected_sig}', "
1199
+ f"got {actual_desc}"
1200
+ )
1201
+
1202
+ def _format_method_signature(
1203
+ self, name: str, params: list[TpyType], return_type: TpyType, readonly: bool,
1204
+ ) -> str:
1205
+ """Short signature string for diagnostic messages."""
1206
+ prefix = "@readonly " if readonly else ""
1207
+ param_strs = ", ".join(str(p) for p in params)
1208
+ param_part = f"self, {param_strs}" if params else "self"
1209
+ return f"{prefix}def {name}({param_part}) -> {return_type}"