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/type_ops.py ADDED
@@ -0,0 +1,1879 @@
1
+ """
2
+ TurboPython Type Operations
3
+
4
+ Type validation, substitution, and inference operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from dataclasses import replace as dc_replace
9
+ from typing import Literal, TYPE_CHECKING
10
+
11
+ from ..typesys import (
12
+ TpyType, TypeParamRef, NominalType, RecursiveAliasInstanceType, PtrType, is_readonly_ptr, OwnType, ReadonlyType, AutoReadonlyType, AutoOwnType,
13
+ make_array, make_list, PendingListType, PendingViewType, GenExprType, SelfType, OptionalType, UnionType,
14
+ TupleType, FinalType, ClassVarType,
15
+ IntLiteralType, FloatLiteralType, TypeParamKind, BIGINT, UnknownElementType,
16
+ NoneType, VoidType, CallableType,
17
+ RecordInfo, FunctionInfo, ParamInfo, is_protocol_type, unwrap_readonly,
18
+ unwrap_ref_type, unwrap_qualifiers, RefType, is_dyn_protocol,
19
+ is_polymorphic_class_type, is_dynamic_dispatch_inner,
20
+ is_callable_type, is_integer_type, is_float_type, is_void_like_type,
21
+ contains_type_param,
22
+ )
23
+ from ..coercions import resolve_coercion, CoercionContext
24
+ from ..diagnostics import SemanticError, nocopy_container_elem_error
25
+ from .. import qnames
26
+ from ..type_def_registry import (
27
+ is_copy_iter, is_own_iter, is_array, is_span, is_list, is_dict, is_set,
28
+ is_enum_type,
29
+ get_type_def, find_factory_by_simple_name, protocol_info_of, is_subtype,
30
+ )
31
+ from ..parse import TpyFunction
32
+ from tpyc import modules as builtin_modules
33
+ from .overloads import resolve_overload
34
+
35
+ if TYPE_CHECKING:
36
+ from ..parse import SourceLocation
37
+ from .context import SemanticContext
38
+ from .protocols import ProtocolChecker
39
+
40
+
41
+ _HASHABLE = NominalType("Hashable", is_protocol=True)
42
+ _EQUATABLE = NominalType("Equatable", is_protocol=True)
43
+
44
+
45
+ def partial_substitute(typ: TpyType, subst: dict[str, TpyType]) -> TpyType:
46
+ """Substitute known type params, preserve unknown TypeParamRefs as-is."""
47
+ if isinstance(typ, TypeParamRef):
48
+ return subst.get(typ.name, typ)
49
+ return typ.map_inner_types(lambda t: partial_substitute(t, subst))
50
+
51
+
52
+ def post_substitute_hint(
53
+ ptype: TpyType, subst: dict[str, TpyType],
54
+ ) -> TpyType | None:
55
+ """Substitute ``subst`` into ``ptype`` and unwrap to a usable hint shape.
56
+
57
+ Strips Own/Readonly/Ref (via ``unwrap_qualifiers``) AND OptionalType so
58
+ the hint reaches consumers (lambda ``is_callable_type`` check, literal
59
+ branches, constructor LHS-hint preference) in a directly-usable inner
60
+ shape -- an ``Optional[T]`` param accepts a bare T arg, so the hint that
61
+ survives substitution should expose T to the inner record/function's seed.
62
+
63
+ Returns None when the substitution is empty or leaves any unbound
64
+ ``TypeParamRef`` -- callers fall back to hint-less analysis in that case.
65
+
66
+ Shared between ``seeded_arg_hint`` (function/method call args, indexed
67
+ via ``ParamInfo``) and ``_analyze_record_constructor``'s inline
68
+ arg-seeding loop (indexed via ``record.init_params`` 3-tuples) so that
69
+ qualifier-stripping rules stay symmetric across the two sites.
70
+ """
71
+ if not subst:
72
+ return None
73
+ hint = partial_substitute(ptype, subst)
74
+ hint = unwrap_qualifiers(hint)
75
+ if isinstance(hint, OptionalType):
76
+ hint = hint.inner
77
+ hint = unwrap_qualifiers(hint)
78
+ if contains_type_param(hint):
79
+ return None
80
+ return hint
81
+
82
+
83
+ def seeded_arg_hint(
84
+ params: list[ParamInfo], idx: int, subst: dict[str, TpyType],
85
+ ) -> TpyType | None:
86
+ """Per-arg ``expr_type_hint`` from a seeded type-param substitution.
87
+
88
+ Resolves the param the call argument at position ``idx`` targets, then
89
+ delegates to ``post_substitute_hint`` for the substitute+unwrap step.
90
+
91
+ Variadic positions: every arg at or beyond the ``*args`` slot maps to
92
+ the variadic param's element type (with the ``Span[readonly[T]]``
93
+ packing stripped), mirroring how ``infer_type_params_for_function``
94
+ matches variadic args.
95
+
96
+ Returns None when there's no valid target param for the index, or when
97
+ ``post_substitute_hint`` rejects the result. Caller dispatches on None
98
+ to fall back to hint-less arg analysis.
99
+ """
100
+ if not subst:
101
+ return None
102
+ if idx < len(params):
103
+ target = params[idx]
104
+ elif params and params[-1].is_variadic:
105
+ target = params[-1]
106
+ else:
107
+ return None
108
+
109
+ ptype = unwrap_ref_type(target.type)
110
+ if target.is_variadic and is_span(ptype):
111
+ # *args: T was lowered to Span[readonly[T]]; expose the element.
112
+ ptype = unwrap_readonly(ptype.type_args[0])
113
+ return post_substitute_hint(ptype, subst)
114
+
115
+
116
+ def _is_useful_seed_binding(t: object) -> bool:
117
+ """True iff ``t`` is a meaningful pre-binding for an LHS-hint seed.
118
+
119
+ Filtered out:
120
+ - "No info" placeholders (None/Void/Unknown from reassignment-narrowing
121
+ LHS like ``x = None; x = f(...)``).
122
+ - Transient pending markers (PendingViewType / PendingListType, which
123
+ ``infer_type_params_for_function`` resolves post-arg-inference anyway).
124
+ - Bare TypeParamRef from an enclosing generic scope: ``seeded_arg_hint``
125
+ itself rejects hints containing TPRefs, but the same binding also
126
+ flows into ``partial_inferred`` in ``_infer_arg_types``' Fn-bearing
127
+ branch -- there it could be compared against concrete arg types and
128
+ silently reject arg-derived evidence.
129
+ - Plain ``int`` values from INT-kind type-param bindings (e.g. Array's
130
+ ``N`` matched against a concrete length): ``partial_substitute``
131
+ ignores INT-kind params (``map_inner_types`` filters them out), so
132
+ they never substitute into per-arg hints, but they would still leak
133
+ into ``partial_inferred`` and conflict with arg-derived ``N`` during
134
+ Phase-2 array matching.
135
+ - ``IntLiteralType`` / ``FloatLiteralType`` literal markers: like the
136
+ pending types, these are transient seed values that
137
+ ``infer_type_params_for_function`` resolves post-arg-inference (to
138
+ the default int/float type). Leaving them in the seed would pin a
139
+ type param to a literal type before arg evidence widens it.
140
+ """
141
+ if isinstance(t, int): # int-kind binding (Array[T, N])
142
+ return False
143
+ return not isinstance(
144
+ t,
145
+ (NoneType, VoidType, UnknownElementType, PendingViewType, PendingListType,
146
+ TypeParamRef, IntLiteralType, FloatLiteralType),
147
+ )
148
+
149
+
150
+ class TypeOperations:
151
+ """Type validation, substitution, and inference operations."""
152
+
153
+ def __init__(self, ctx: SemanticContext):
154
+ self.ctx = ctx
155
+ # Set after construction (ProtocolChecker depends on TypeOperations,
156
+ # so it can't be constructed before us). Mirrors the deferred-wiring
157
+ # pattern in TypeCompatibility. Used by validate_hashable_container_elem
158
+ # for Hashable + Equatable conformance.
159
+ self.protocols: ProtocolChecker
160
+
161
+ def resolve_type(self, typ: TpyType, *, protocols_only: bool = False) -> TpyType:
162
+ """Sema-local type normalization.
163
+
164
+ Runs on already-resolved `TpyType` values from the parser-side
165
+ `TypeResolver` and applies the transformations that depend on
166
+ sema state:
167
+
168
+ - Bare `NominalType` matching a type parameter in the current
169
+ function / record scope becomes `TypeParamRef` (parser emits
170
+ `NominalType("T")` because type-param scope is scope-local
171
+ to sema, not parser).
172
+ - Compile-time-only aliases (e.g. `FStr` singleton) expand to
173
+ their target type.
174
+ - Structural wrappers (`PtrType`, `OwnType`, ...) recurse into
175
+ their inner types so both transformations above reach deeply
176
+ nested positions.
177
+
178
+ Args:
179
+ protocols_only: If True, skip TypeParamRef conversion.
180
+ Used during record registration when the type-parameter
181
+ scope is not yet active.
182
+ """
183
+ if isinstance(typ, NominalType):
184
+ if not protocols_only and not typ.type_args and not typ.is_protocol:
185
+ func = self.ctx.func.current_function
186
+ if isinstance(func, TpyFunction) and typ.name in func.type_params:
187
+ bound = func.type_param_bounds.get(typ.name)
188
+ return TypeParamRef(typ.name, bound=bound)
189
+ if typ.name in (self.ctx.record_ctx.type_params or []):
190
+ return TypeParamRef(typ.name)
191
+ if not typ.type_args and self.ctx.registry.type_aliases:
192
+ alias = self.ctx.registry.get_type_alias(typ.name)
193
+ if alias is not None and alias.is_compile_time_only():
194
+ return alias
195
+ if typ.type_args:
196
+ new_args = tuple(
197
+ self.resolve_type(arg, protocols_only=protocols_only) if isinstance(arg, TpyType) else arg
198
+ for arg in typ.type_args
199
+ )
200
+ if any(new is not old for new, old in zip(new_args, typ.type_args)):
201
+ new_inner = tuple(a for a in new_args if isinstance(a, TpyType))
202
+ return typ.with_inner_types(new_inner)
203
+ return typ
204
+ if isinstance(typ, PtrType):
205
+ resolved = self.resolve_type(typ.pointee, protocols_only=protocols_only)
206
+ return PtrType(resolved, is_readonly=typ.is_readonly) if resolved is not typ.pointee else typ
207
+ if isinstance(typ, (OwnType, ReadonlyType, AutoReadonlyType, AutoOwnType)):
208
+ resolved = self.resolve_type(typ.wrapped, protocols_only=protocols_only)
209
+ return type(typ)(resolved) if resolved is not typ.wrapped else typ
210
+ if isinstance(typ, OptionalType):
211
+ resolved = self.resolve_type(typ.inner, protocols_only=protocols_only)
212
+ if resolved is typ.inner:
213
+ return typ
214
+ return typ.with_inner(resolved)
215
+ if isinstance(typ, UnionType):
216
+ resolved = tuple(self.resolve_type(m, protocols_only=protocols_only) for m in typ.members)
217
+ return UnionType(resolved) if any(a is not b for a, b in zip(resolved, typ.members)) else typ
218
+ if isinstance(typ, TupleType):
219
+ resolved = tuple(self.resolve_type(e, protocols_only=protocols_only) for e in typ.element_types)
220
+ return TupleType(resolved) if any(a is not b for a, b in zip(resolved, typ.element_types)) else typ
221
+ return typ
222
+
223
+ def validate_type(
224
+ self, typ: TpyType, allow_type_param_ref: bool = False, loc: SourceLocation | None = None,
225
+ allow_forward_ref: bool = True, check_hashable_constraints: bool = True,
226
+ allow_pointer_repr_dynamic: bool = False,
227
+ ) -> None:
228
+ """Validate that a type is well-formed.
229
+
230
+ Args:
231
+ typ: The type to validate.
232
+ allow_type_param_ref: If True, TypeParamRef is allowed (for generic class definitions).
233
+ loc: Optional source location for error messages.
234
+ allow_forward_ref: If True, unknown NominalType records are allowed (for class registration).
235
+ allow_pointer_repr_dynamic: If True, a top-level `Optional[@dynamic protocol]`
236
+ is allowed -- it lowers to a `const P*` borrow, which is sound at a
237
+ parameter position. Reset for any nested position (storage / container /
238
+ Own slot), where the value-repr `std::optional<P>` of an abstract base is
239
+ not representable.
240
+ """
241
+ # Closure-captured recurse helper: every internal recursive call uses
242
+ # this so the four contextual params (`allow_type_param_ref`, `loc`,
243
+ # `allow_forward_ref`, `check_hashable_constraints`) propagate
244
+ # automatically into nested wrapper / container / tuple branches.
245
+ # Manual kwarg-forwarding at each site was a recurring source of
246
+ # flag-drop bugs (`Own[set[Forward]]` field false-rejecting because
247
+ # the OwnType recursion reset `check_hashable_constraints=False`
248
+ # back to the default True). The closure makes the drop structurally
249
+ # impossible -- adding a new wrapper branch in the future cannot
250
+ # forget to forward.
251
+ def _recurse(inner: TpyType) -> None:
252
+ # allow_pointer_repr_dynamic is deliberately NOT forwarded here: a
253
+ # nested position -- container element, Own slot, tuple member -- is
254
+ # storage-shaped, where Optional[@dynamic P] has no valid value-repr,
255
+ # so it must stay rejected. The transparent readonly wrappers are the
256
+ # exception and preserve the flag explicitly in their own branch.
257
+ self.validate_type(
258
+ inner, allow_type_param_ref, loc,
259
+ allow_forward_ref=allow_forward_ref,
260
+ check_hashable_constraints=check_hashable_constraints,
261
+ )
262
+
263
+ if isinstance(typ, TypeParamRef):
264
+ if not allow_type_param_ref:
265
+ raise SemanticError(f"Type parameter '{typ.name}' used outside of generic context", loc)
266
+ if typ.kind == TypeParamKind.INT:
267
+ raise SemanticError(f"Integer type parameter '{typ.name}' cannot be used as a type annotation", loc)
268
+ return
269
+ if isinstance(typ, NominalType) and typ.is_record:
270
+ if typ.qualified_name() == "builtins.type":
271
+ raise SemanticError("'type' cannot be used as a type annotation", loc)
272
+ # Unified arity/validity check. See docs/ARCHITECTURE.md:
273
+ # validity comes from qname/TypeDef/factory/record registry, not
274
+ # from `_module_qname != None`. Prefer user-record info (may
275
+ # shadow builtin names), then fall through to the factory
276
+ # (covers bare-name NominalType like def f(x: list) that the
277
+ # parser leaves without `_module_qname`).
278
+ record_info = self.ctx.registry.get_record_for_type(typ)
279
+ kinds: tuple | None = None
280
+ if record_info is None:
281
+ # Primary path: resolve via the fully qualified name the type
282
+ # already knows. Parser leaves some bare-name builtins without
283
+ # _module_qname (e.g. `def f(x: list)`), so qualified_name()
284
+ # returns None or just the simple name; for those we scan
285
+ # `builtins` + `tpy` by simple name as the factory-aware
286
+ # fallback. Either way a single TypeDef lookup wins.
287
+ qn = typ.qualified_name()
288
+ td = get_type_def(qn) if qn else None
289
+ if td is None or td.type_factory is None:
290
+ td = find_factory_by_simple_name(typ.name)
291
+ if td is not None and td.type_factory is not None:
292
+ kinds = td.param_kinds
293
+ if record_info is not None:
294
+ if typ.type_args:
295
+ if not record_info.is_generic():
296
+ raise SemanticError(f"Record '{typ.name}' is not generic, but type arguments were provided", loc)
297
+ if len(typ.type_args) != len(record_info.type_params):
298
+ raise SemanticError(
299
+ f"Record '{typ.name}' expects {len(record_info.type_params)} type arguments, "
300
+ f"got {len(typ.type_args)}",
301
+ loc,
302
+ )
303
+ self.validate_record_type_args(
304
+ typ, record_info, allow_type_param_ref, loc,
305
+ allow_forward_ref=allow_forward_ref,
306
+ check_hashable_constraints=check_hashable_constraints,
307
+ )
308
+ elif record_info.is_generic():
309
+ raise SemanticError(
310
+ f"Generic record '{typ.name}' requires type arguments: "
311
+ f"{typ.name}[{', '.join(record_info.type_params)}]",
312
+ loc,
313
+ )
314
+ elif kinds is not None:
315
+ # Factory-only builtin: primitive singleton (empty kinds) or
316
+ # generic container (list, dict, set, Array, Span, ...). Arity
317
+ # mismatch -- including the bare-generic case -- is an error.
318
+ if len(kinds) != len(typ.type_args):
319
+ if not typ.type_args and kinds:
320
+ raise SemanticError(
321
+ f"Generic type '{typ.name}' requires "
322
+ f"{len(kinds)} type argument{'s' if len(kinds) != 1 else ''}",
323
+ loc,
324
+ )
325
+ if typ.type_args and not kinds:
326
+ raise SemanticError(
327
+ f"Type '{typ.name}' is not generic, but type arguments were provided",
328
+ loc,
329
+ )
330
+ raise SemanticError(
331
+ f"Type '{typ.name}' expects {len(kinds)} type argument"
332
+ f"{'s' if len(kinds) != 1 else ''}, got {len(typ.type_args)}",
333
+ loc,
334
+ )
335
+ elif self.ctx.registry.get_enum(typ.name) is not None:
336
+ pass # imported enum -- NominalType will be resolved to EnumType
337
+ elif self.ctx.registry.get_type_alias(typ.name) is not None:
338
+ # Type alias name. Recursive aliases like
339
+ # `type Tree = int | list[Tree]` leave a bare
340
+ # NominalType("Tree") placeholder inside the body that
341
+ # resolve_type expands but validate_type still encounters
342
+ # during deep recursion -- accept it as a known alias
343
+ # without further validation (the alias's body is itself
344
+ # validated when registered).
345
+ pass
346
+ elif not allow_forward_ref:
347
+ raise SemanticError(f"Unknown type: {typ.name}", loc)
348
+ # Container element validation (containers are NominalType + TypeDef)
349
+ elem_type = typ.get_element_type()
350
+ if elem_type is not None:
351
+ _recurse(elem_type)
352
+ if is_protocol_type(elem_type):
353
+ raise SemanticError(
354
+ f"Protocol type '{elem_type}' cannot be used as a container element type",
355
+ loc,
356
+ )
357
+ # Annotation-time gate for set/dict K. Deferred during early
358
+ # record registration -- sibling records' methods aren't
359
+ # populated yet, so we'd false-reject; validate_record_field_protocols
360
+ # re-runs us post-registration with the default flag.
361
+ if (is_set(typ) or is_dict(typ)) and check_hashable_constraints \
362
+ and not allow_type_param_ref:
363
+ key_or_elem = typ.type_args[0] if typ.type_args else None
364
+ if key_or_elem is not None and isinstance(key_or_elem, TpyType):
365
+ kind = "dict key" if is_dict(typ) else "set element"
366
+ self.validate_hashable_container_elem(key_or_elem, kind, loc)
367
+ elif isinstance(typ, OptionalType):
368
+ _recurse(typ.inner)
369
+ # Reject `Optional[Own[Polymorphic]]`: a polymorphic Own lowers to
370
+ # `unique_ptr<P>` (P abstract / a @dynamic protocol), so the
371
+ # Optional slot is `optional<unique_ptr<P>>` -- a double indirection
372
+ # the member-access, call-site, and isinstance lowerings do not
373
+ # thread (a concrete Own collapses to `optional<P>` by value and
374
+ # stays supported). Point users at `Optional[Box[P]]`, the
375
+ # idiomatic nullable owned-polymorphic form. Mirrors the
376
+ # `Own[Optional[Polymorphic]]` rejection in the OwnType branch.
377
+ own_inner = unwrap_readonly(typ.inner)
378
+ if isinstance(own_inner, OwnType):
379
+ own_pointee = unwrap_readonly(own_inner.wrapped)
380
+ if (isinstance(own_pointee, NominalType)
381
+ and (is_polymorphic_class_type(own_pointee, self.ctx.registry)
382
+ or is_dynamic_dispatch_inner(own_pointee, self.ctx.registry))):
383
+ raise SemanticError(
384
+ f"`Optional[Own[{own_pointee.name}]]` is not yet "
385
+ f"supported: a polymorphic `Own[{own_pointee.name}]` "
386
+ f"lowers to `unique_ptr<{own_pointee.name}>`, so the "
387
+ f"optional slot is a double indirection that member "
388
+ f"access and isinstance do not thread today. Use "
389
+ f"`Optional[Box[{own_pointee.name}]]` for a nullable "
390
+ f"owned polymorphic value instead.",
391
+ loc,
392
+ )
393
+ if is_protocol_type(typ.inner) and not allow_pointer_repr_dynamic:
394
+ proto_def = protocol_info_of(typ.inner)
395
+ if proto_def and proto_def.is_dynamic:
396
+ raise SemanticError(
397
+ f"Optional[{typ.inner}] is only supported at a parameter "
398
+ f"position (where it lowers to a `const {typ.inner}*` "
399
+ f"borrow). In a field, return, local, container, or owned "
400
+ f"slot it has no value representation; use `Optional"
401
+ f"[Box[{typ.inner}]]` for owned storage or a separate "
402
+ f"'has' flag instead",
403
+ loc,
404
+ )
405
+ elif isinstance(typ, UnionType):
406
+ non_none = [m for m in typ.members if not is_void_like_type(m)]
407
+ protocols = [m for m in non_none if is_protocol_type(m)]
408
+ concrete = [m for m in non_none if not is_protocol_type(m)]
409
+ if protocols and concrete:
410
+ proto_names = ", ".join(f"'{m}'" for m in protocols)
411
+ raise SemanticError(
412
+ f"Cannot mix protocol types ({proto_names}) with concrete types in a union",
413
+ loc,
414
+ )
415
+ if protocols:
416
+ for p in protocols:
417
+ proto_def = protocol_info_of(p)
418
+ if proto_def and proto_def.is_dynamic:
419
+ raise SemanticError(
420
+ f"@dynamic protocol '{p}' cannot be used in a protocol union; "
421
+ f"only static protocols are supported",
422
+ loc,
423
+ )
424
+ for member in typ.members:
425
+ _recurse(member)
426
+ elif isinstance(typ, RefType):
427
+ # Ref-wrapped param/return types -- recurse into the underlying
428
+ # type. Without this branch, validate_type silently bottoms out
429
+ # at the `get_element_type()` fallback (Ref forwards element
430
+ # extraction to the wrapped, which returns None for class /
431
+ # Own / Optional / etc.), bypassing all checks on method params.
432
+ _recurse(typ.wrapped)
433
+ elif isinstance(typ, TupleType):
434
+ # Recurse element-wise. Without this branch, tuple element types
435
+ # (e.g. `Own[Optional[Polymorphic]]` nested in `tuple[..., ...]`)
436
+ # bypass every recursive rejection in this function, because
437
+ # `TupleType.get_element_type()` returns None and the
438
+ # `NominalType` container-element fallback at the tail of
439
+ # this elif chain never fires for tuples.
440
+ for member in typ.element_types:
441
+ _recurse(member)
442
+ elif isinstance(typ, OwnType):
443
+ _recurse(typ.wrapped)
444
+ # Reject `Own[Optional[Polymorphic]]`: an Own-Optional slot of
445
+ # a polymorphic class is laid out for the base only, so a
446
+ # derived value stored into it would slice, and isinstance
447
+ # dispatch has no valid pointer lowering. `is_polymorphic_class_type`
448
+ # returns False for unregistered records (forward-ref case),
449
+ # so this gate naturally defers to the post-registration
450
+ # revalidation pass like the Optional[@dynamic] check above.
451
+ opt = unwrap_readonly(typ.wrapped)
452
+ if isinstance(opt, OptionalType):
453
+ opt_inner = unwrap_readonly(opt.inner)
454
+ else:
455
+ opt_inner = None
456
+ if (isinstance(opt, OptionalType)
457
+ and isinstance(opt_inner, NominalType)
458
+ and is_polymorphic_class_type(opt_inner, self.ctx.registry)):
459
+ cls = opt_inner.name
460
+ raise SemanticError(
461
+ f"`Own[Optional[{cls}]]` is not yet supported: an "
462
+ f"Own-Optional slot of a polymorphic class is laid "
463
+ f"out for `{cls}` only, so storing a derived class "
464
+ f"into it would slice the dynamic type and isinstance "
465
+ f"dispatch on the slot has no valid lowering today. "
466
+ f"Use `Optional[{cls}]` (borrowed pointer that may "
467
+ f"be None) or `Box[{cls}]` (owned, polymorphism-"
468
+ f"preserving) instead.",
469
+ loc,
470
+ )
471
+ elif isinstance(typ, (ReadonlyType, AutoReadonlyType)):
472
+ # readonly is a transparent same-position wrapper, so preserve
473
+ # allow_pointer_repr_dynamic across it: readonly[Optional[@dynamic P]]
474
+ # at a parameter is still a pointer-repr borrow (const P*), unlike
475
+ # the container/Own/tuple positions where _recurse resets the flag.
476
+ self.validate_type(
477
+ typ.wrapped, allow_type_param_ref, loc,
478
+ allow_forward_ref=allow_forward_ref,
479
+ check_hashable_constraints=check_hashable_constraints,
480
+ allow_pointer_repr_dynamic=allow_pointer_repr_dynamic,
481
+ )
482
+ elif isinstance(typ, AutoOwnType):
483
+ # auto_own[T] is stripped before sema body analysis, but
484
+ # registration may still call validate_type on a freshly-parsed
485
+ # return type before the strip pass runs. Mirror the AutoReadonly
486
+ # branch so any pre-strip residue is still validated.
487
+ _recurse(typ.wrapped)
488
+ elif isinstance(typ, (FinalType, ClassVarType)):
489
+ # `Final[T]` / `ClassVar[T]` annotations. These are stripped
490
+ # during sema body analysis, but variable type annotations are
491
+ # validated BEFORE the strip (see `statements.py::_analyze_var_decl`),
492
+ # so an unrecognized `Final[Unknown]` would otherwise fall
493
+ # through to the tail `get_element_type` fallback as a no-op.
494
+ _recurse(typ.wrapped)
495
+ elif isinstance(typ, PtrType):
496
+ _recurse(typ.pointee)
497
+ if is_protocol_type(typ.pointee):
498
+ # Only static protocols are rejected. @dynamic protocols
499
+ # carry a runtime vtable, so `Ptr[P]` for @dynamic P is a
500
+ # well-defined non-owning protocol reference. If
501
+ # protocol_info_of is None here, the protocol isn't yet
502
+ # registered (record fields validate before protocols);
503
+ # defer to validate_record_field_protocols' re-pass,
504
+ # which runs post-registration and re-fires this check.
505
+ proto_def = protocol_info_of(typ.pointee)
506
+ if proto_def is not None and not proto_def.is_dynamic:
507
+ raise SemanticError(
508
+ f"Static protocol type '{typ.pointee}' cannot be used as a pointer element type "
509
+ f"(only @dynamic protocols, which carry a runtime vtable, can)",
510
+ loc,
511
+ )
512
+ elif (elem_type := typ.get_element_type()) is not None:
513
+ _recurse(elem_type)
514
+ if is_protocol_type(elem_type):
515
+ raise SemanticError(
516
+ f"Protocol type '{elem_type}' cannot be used as a container element type",
517
+ loc,
518
+ )
519
+
520
+ def validate_hashable_container_elem(
521
+ self, elem: TpyType, kind: Literal["set element", "dict key"],
522
+ loc: SourceLocation | None,
523
+ ) -> None:
524
+ """Validate that `elem` can be used as a set element / dict key.
525
+
526
+ `kind` is "set element" or "dict key" and feeds into diagnostics.
527
+ Hash-table-backed ordered_set / ordered_map store entries in
528
+ `std::pair<const K, ...>`, which requires copy-constructible K;
529
+ @nocopy types are rejected first with a precise message so the
530
+ user doesn't hit a wall of C++ template errors from container
531
+ internals. Hashability is the second gate: a non-hashable K
532
+ otherwise fails C++ build with `static_assert(__is_invocable<
533
+ const _Hash&, const _Key&>{})`.
534
+
535
+ Called both from annotation-time validation (validate_type, above)
536
+ and from literal/comprehension expression analysis (4 callers in
537
+ sema/expressions.py).
538
+ """
539
+ if isinstance(elem, OwnType):
540
+ elem = elem.wrapped
541
+ if isinstance(elem, IntLiteralType):
542
+ return
543
+ if is_enum_type(elem):
544
+ return
545
+ if isinstance(elem, PendingViewType):
546
+ return
547
+ if self.ctx.is_type_non_copyable(elem):
548
+ raise SemanticError(nocopy_container_elem_error(elem, kind), loc)
549
+ # Need BOTH Hashable AND Equatable -- ordered_set/ordered_map use
550
+ # std::hash (requires __hash__) and std::equal_to (requires __eq__).
551
+ # Conformance walks the inheritance chain, so `class Child(Base)`
552
+ # where Base defines either dunder is accepted via Base.
553
+ hashable_ok = self.protocols.type_conforms_to_protocol(elem, _HASHABLE)
554
+ equatable_ok = self.protocols.type_conforms_to_protocol(elem, _EQUATABLE)
555
+ if hashable_ok and equatable_ok:
556
+ return
557
+ # User-record specific guidance: tell the user exactly which dunder
558
+ # is missing instead of the generic "not hashable" message.
559
+ if isinstance(elem, NominalType) and elem.is_user_record:
560
+ missing = []
561
+ if not hashable_ok:
562
+ missing.append("__hash__")
563
+ if not equatable_ok:
564
+ missing.append("__eq__")
565
+ missing_str = " and ".join(missing)
566
+ pronoun = "them" if len(missing) > 1 else "it"
567
+ raise SemanticError(
568
+ f"Type '{elem}' cannot be used as a {kind} "
569
+ f"(missing {missing_str}; "
570
+ f"use @dataclass(frozen=True) or define {pronoun} explicitly)",
571
+ loc,
572
+ )
573
+ raise SemanticError(
574
+ f"Type '{elem}' cannot be used as a {kind} (not hashable)", loc,
575
+ )
576
+
577
+ def validate_record_type_args(
578
+ self, typ: NominalType, record_info: RecordInfo, allow_type_param_ref: bool = False,
579
+ loc: SourceLocation | None = None,
580
+ allow_forward_ref: bool = True, check_hashable_constraints: bool = True,
581
+ ) -> None:
582
+ """Validate that type arguments match their expected kinds (TYPE vs INT).
583
+
584
+ Args:
585
+ typ: The NominalType with type_args to validate.
586
+ record_info: The RecordInfo with type_param_kinds.
587
+ allow_type_param_ref: If True, allow TypeParamRef as valid types.
588
+ loc: Optional source location for error messages.
589
+ allow_forward_ref / check_hashable_constraints: forwarded to the
590
+ recursive `validate_type` calls so wrapper-form type args
591
+ (`MyGeneric[set[Forward]]`) honor the outer caller's deferral.
592
+ """
593
+ # Same closure pattern as `validate_type` -- one binding so flag
594
+ # forwarding can't drift across the two recursive type-arg sites.
595
+ def _recurse(inner: TpyType) -> None:
596
+ self.validate_type(
597
+ inner, allow_type_param_ref, loc,
598
+ allow_forward_ref=allow_forward_ref,
599
+ check_hashable_constraints=check_hashable_constraints,
600
+ )
601
+
602
+ if not record_info.type_param_kinds:
603
+ # Legacy: no kinds specified, assume all TYPE
604
+ for arg in typ.type_args:
605
+ if isinstance(arg, TpyType):
606
+ _recurse(arg)
607
+ else:
608
+ raise SemanticError(
609
+ f"Record '{typ.name}' does not accept integer type arguments",
610
+ loc,
611
+ )
612
+ return
613
+
614
+ for i, (arg, kind) in enumerate(zip(typ.type_args, record_info.type_param_kinds)):
615
+ param_name = record_info.type_params[i]
616
+ if kind == TypeParamKind.INT:
617
+ # Expect an integer value or INT TypeParamRef (forwarding)
618
+ if isinstance(arg, int):
619
+ continue # Valid: integer literal
620
+ if isinstance(arg, TypeParamRef) and arg.kind == TypeParamKind.INT:
621
+ continue # Valid: forwarding an INT type param
622
+ raise SemanticError(
623
+ f"Type parameter '{param_name}' of '{typ.name}' requires an integer, "
624
+ f"got {arg}",
625
+ loc,
626
+ )
627
+ else:
628
+ # Expect a type value
629
+ if isinstance(arg, int):
630
+ raise SemanticError(
631
+ f"Type parameter '{param_name}' of '{typ.name}' requires a type, "
632
+ f"got integer {arg}",
633
+ loc,
634
+ )
635
+ if isinstance(arg, TpyType):
636
+ _recurse(arg)
637
+ else:
638
+ raise SemanticError(
639
+ f"Invalid type argument for '{param_name}' of '{typ.name}': {arg}",
640
+ loc,
641
+ )
642
+
643
+ def substitute_type_params(self, typ: TpyType, subst: dict[str, TpyType | int]) -> TpyType:
644
+ """Substitute type parameters with concrete types.
645
+
646
+ Args:
647
+ typ: The type containing potential TypeParamRef instances.
648
+ subst: Mapping from type parameter names to concrete types or integers.
649
+
650
+ Returns:
651
+ The type with all TypeParamRef instances replaced by their concrete types.
652
+ """
653
+ # PERF TODO: candidate for memoization by (typ, frozen(subst)) if profile
654
+ # shows this is hot. Frozen dataclass types are hashable. Needs profiling
655
+ # data post-mypyc before acting; not a top hotspot in current profiles.
656
+ if isinstance(typ, TypeParamRef):
657
+ if typ.name in subst:
658
+ replacement = subst[typ.name]
659
+ if isinstance(replacement, int):
660
+ # INT type params: keep as TypeParamRef for expression contexts (codegen uses the name)
661
+ # Type-level substitution for Array etc. is handled below
662
+ return typ
663
+ return replacement
664
+ raise SemanticError(f"Unknown type parameter '{typ.name}'")
665
+ # Special handling for OptionalType: preserve T* repr from the template.
666
+ # When the template used T* (unbounded TypeParamRef -> not a value type),
667
+ # but the concrete type after substitution is a value type, force pointer
668
+ # repr so the caller matches the template's representation.
669
+ if isinstance(typ, OptionalType) and typ.uses_pointer_repr():
670
+ new_inner = self.substitute_type_params(typ.inner, subst)
671
+ if new_inner.is_value_type():
672
+ return OptionalType(new_inner, force_pointer_repr=True)
673
+ return OptionalType(new_inner)
674
+ # Special handling for Array: substitute size if it's a TypeParamRef
675
+ if is_array(typ):
676
+ elem, size = typ.type_args[0], typ.type_args[1]
677
+ new_elem = self.substitute_type_params(elem, subst)
678
+ new_size = size
679
+ if isinstance(size, TypeParamRef) and size.name in subst:
680
+ new_size = subst[size.name]
681
+ if new_elem != elem or new_size != size:
682
+ return make_array(new_elem, new_size)
683
+ return typ
684
+ # NominalType (user records and module-defined generics like
685
+ # UninitArrayStorage[T, N]) can have mixed TpyType/int type_args.
686
+ # map_inner_types operates on TpyType -> TpyType, so it can't
687
+ # substitute int-valued TypeParamRefs. Same reason as Array above
688
+ # needs direct handling. Exact type check avoids catching NominalType
689
+ # subclasses which don't have int args.
690
+ if type(typ) is NominalType and typ.type_args:
691
+ new_args: list[TpyType | int] = []
692
+ changed = False
693
+ for arg in typ.type_args:
694
+ if isinstance(arg, TypeParamRef) and arg.name in subst:
695
+ new_args.append(subst[arg.name])
696
+ changed = True
697
+ elif isinstance(arg, TpyType):
698
+ new_arg = self.substitute_type_params(arg, subst)
699
+ new_args.append(new_arg)
700
+ if new_arg is not arg:
701
+ changed = True
702
+ else:
703
+ new_args.append(arg) # already-concrete int value
704
+ if changed:
705
+ return NominalType(typ.name, tuple(new_args), typ.is_protocol,
706
+ typ._module_qname, typ.is_dynamic_protocol)
707
+ return typ
708
+ # Use map_inner_types for types that have inner types
709
+ result = typ.map_inner_types(lambda t: self.substitute_type_params(t, subst))
710
+ # Ptr[Ref[T]] -> Ptr[T]: Ref inside Ptr is redundant (Ptr is
711
+ # already a pointer; the Ref from make_ref should not nest).
712
+ if isinstance(result, PtrType) and isinstance(result.pointee, RefType):
713
+ result = PtrType(result.pointee.wrapped, result.is_readonly)
714
+ return result
715
+
716
+ def build_type_substitution(self, record_type: TpyType) -> dict[str, TpyType | int]:
717
+ """Build a type parameter substitution map for a generic record instantiation.
718
+
719
+ Works for all types: user records and module types (NominalType) use
720
+ RecordInfo.type_params + type_args; non-NominalType builtins (
721
+ etc.) use extract_type_params from the module system.
722
+
723
+ Returns:
724
+ Mapping from type parameter names to concrete types or integers.
725
+ For example: {"T": Int32, "N": 8} for Matrix[Int32, 8].
726
+ """
727
+ if isinstance(record_type, NominalType):
728
+ record_info = self.ctx.registry.get_record_for_type(record_type)
729
+ if not record_info or not record_info.is_generic():
730
+ return {}
731
+ if not record_type.type_args:
732
+ return {}
733
+ return dict(zip(record_info.type_params, record_type.type_args))
734
+ return builtin_modules.extract_type_params(record_type)
735
+
736
+ def substitute_types(self, typ: TpyType, subst: dict[str, TpyType]) -> TpyType:
737
+ """Recursively substitute types throughout a type structure.
738
+
739
+ Substitutes SelfType and TypeParamRef according to the substitution map.
740
+ Handles nested types like Own[Self], Ptr[T], list[T], etc.
741
+ Uses map_inner_types for generic traversal of wrapper types.
742
+ """
743
+ if isinstance(typ, SelfType) and "Self" in subst:
744
+ return subst["Self"]
745
+ if isinstance(typ, TypeParamRef) and typ.name in subst:
746
+ return subst[typ.name]
747
+ return typ.map_inner_types(lambda t: self.substitute_types(t, subst))
748
+
749
+ def substitute_self(self, typ: TpyType, actual: TpyType) -> TpyType:
750
+ """Recursively substitute SelfType with actual type throughout a type structure.
751
+
752
+ Handles nested types like Own[Self], Ptr[Self], list[Self], etc.
753
+ Uses map_inner_types for generic traversal of wrapper types.
754
+ """
755
+ return self.substitute_types(typ, {"Self": actual})
756
+
757
+ def is_forwarded_type_param(self, typ: TpyType, type_params: list[str]) -> bool:
758
+ """Check if a type references one of the given type parameters.
759
+
760
+ At parse time, type parameters in base class type args appear as NominalType
761
+ (e.g., Container[T] has T as NominalType("T"), not TypeParamRef("T")).
762
+ This function checks for both forms.
763
+ """
764
+ if isinstance(typ, TypeParamRef):
765
+ return typ.name in type_params
766
+ if isinstance(typ, NominalType):
767
+ # A NominalType with no type_args and name matching a type param is a forwarded param
768
+ if not typ.type_args and typ.name in type_params:
769
+ return True
770
+ # Also check nested type args (e.g., Container[list[T]] or Parent[Sequence[T]])
771
+ for type_arg in typ.type_args:
772
+ if isinstance(type_arg, TpyType) and self.is_forwarded_type_param(type_arg, type_params):
773
+ return True
774
+ if isinstance(typ, PtrType):
775
+ return self.is_forwarded_type_param(typ.pointee, type_params)
776
+ if isinstance(typ, OwnType):
777
+ return self.is_forwarded_type_param(typ.wrapped, type_params)
778
+ if isinstance(typ, ReadonlyType):
779
+ return self.is_forwarded_type_param(typ.wrapped, type_params)
780
+ return False
781
+
782
+ def get_type_param_bound(self, type_param_name: str) -> TpyType | None:
783
+ """Look up the bound for a type parameter from current context.
784
+
785
+ Checks current function's type_param_bounds first, then record's.
786
+ Returns None if no bound is declared.
787
+ """
788
+ # Check current function's type param bounds
789
+ if (self.ctx.func.current_function and isinstance(self.ctx.func.current_function, TpyFunction)
790
+ and type_param_name in self.ctx.func.current_function.type_param_bounds):
791
+ return self.ctx.func.current_function.type_param_bounds[type_param_name]
792
+ # Check current record's type param bounds (for methods in generic classes)
793
+ if (self.ctx.record_ctx.type_param_bounds
794
+ and type_param_name in self.ctx.record_ctx.type_param_bounds):
795
+ return self.ctx.record_ctx.type_param_bounds[type_param_name]
796
+ return None
797
+
798
+ def match_type_with_inference(
799
+ self,
800
+ param_type: TpyType,
801
+ arg_type: TpyType,
802
+ inferred: dict[str, TpyType]
803
+ ) -> bool:
804
+ """Match param_type against arg_type, collecting type param inferences.
805
+
806
+ Returns True if types match (with inference), False otherwise.
807
+ """
808
+ # RefType wrapper on param: strip Ref from param side and also strip
809
+ # from arg if present (Ref[T] param accepts both T and Ref[T] args).
810
+ if isinstance(param_type, RefType):
811
+ return self.match_type_with_inference(param_type.wrapped, unwrap_ref_type(arg_type), inferred)
812
+ # Ref on arg side is preserved for type param inference -- this lets
813
+ # map(identity, pts) infer U=Ref[Point] from identity's return type,
814
+ # so the template emits val_or_ref<Point> for reference preservation.
815
+
816
+ # ReadonlyType wrapper: unwrap for matching (readonly[T] accepts T and readonly[T])
817
+ if isinstance(param_type, ReadonlyType):
818
+ arg_unwrapped = unwrap_readonly(arg_type)
819
+ return self.match_type_with_inference(param_type.wrapped, arg_unwrapped, inferred)
820
+
821
+ # Own[T] wrapper on argument -- unwrap before matching.
822
+ # Own is an ownership marker (e.g. from copy()), not a distinct type.
823
+ if isinstance(arg_type, OwnType):
824
+ arg_type = arg_type.wrapped
825
+
826
+ # TypeParamRef -- infer or check consistency
827
+ if isinstance(param_type, TypeParamRef):
828
+ if param_type.name in inferred:
829
+ existing = inferred[param_type.name]
830
+ # UnknownElementType is a "no info" placeholder from an empty
831
+ # container literal. Either side can be it; the concrete side
832
+ # wins so `f([1], [])` and `f([], [1])` infer T identically.
833
+ if isinstance(existing, UnknownElementType):
834
+ inferred[param_type.name] = arg_type
835
+ return True
836
+ if isinstance(arg_type, UnknownElementType):
837
+ return True
838
+ if isinstance(existing, IntLiteralType) and is_integer_type(arg_type):
839
+ inferred[param_type.name] = arg_type
840
+ return True
841
+ if isinstance(existing, FloatLiteralType) and is_float_type(arg_type):
842
+ inferred[param_type.name] = arg_type
843
+ return True
844
+ # Ref[T] matches bare T: the same type param can be inferred
845
+ # as bare T (from iterables) and Ref[T] (from function returns).
846
+ # Accept both directions without conflict.
847
+ if isinstance(arg_type, RefType) and arg_type.wrapped == existing:
848
+ return True
849
+ if isinstance(existing, RefType) and existing.wrapped == arg_type:
850
+ return True
851
+ return self.types_match_for_inference(existing, arg_type)
852
+ inferred[param_type.name] = arg_type
853
+ return True
854
+
855
+ # Protocol with TypeParamRef in type_args (e.g., NativeIterable[T],
856
+ # NativeIterable[tuple[K, V]]) -- TypeParamRefs may be nested
857
+ if is_protocol_type(param_type) and param_type.type_args:
858
+ if any(isinstance(t, TpyType) and contains_type_param(t)
859
+ for t in param_type.type_args):
860
+ return self._match_protocol_type_args_with_inference(param_type, arg_type, inferred)
861
+
862
+ # PtrType with TypeParamRef pointee (e.g., Ptr[T], Ptr[readonly[T]])
863
+ if isinstance(param_type, PtrType):
864
+ if not isinstance(arg_type, PtrType):
865
+ return False
866
+ if arg_type.is_readonly and not param_type.is_readonly:
867
+ return False
868
+ return self.match_type_with_inference(
869
+ param_type.pointee, arg_type.pointee, inferred
870
+ )
871
+
872
+ # list[T] with nested TypeParamRef (e.g., list[T])
873
+ if is_list(param_type):
874
+ if is_list(arg_type):
875
+ return self.match_type_with_inference(
876
+ param_type.type_args[0], arg_type.type_args[0], inferred
877
+ )
878
+ if isinstance(arg_type, PendingListType):
879
+ return self.match_type_with_inference(
880
+ param_type.type_args[0], arg_type.element_type, inferred
881
+ )
882
+ return False
883
+
884
+ # TupleType with TypeParamRef elements (e.g., tuple[T, T])
885
+ if isinstance(param_type, TupleType):
886
+ if not isinstance(arg_type, TupleType):
887
+ return False
888
+ if len(param_type.element_types) != len(arg_type.element_types):
889
+ return False
890
+ return all(
891
+ self.match_type_with_inference(p, a, inferred)
892
+ for p, a in zip(param_type.element_types, arg_type.element_types)
893
+ )
894
+
895
+ # Array with TypeParamRef element or size (e.g., Array[T, N])
896
+ if is_array(param_type):
897
+ return self._match_array_with_inference(param_type, arg_type, inferred)
898
+
899
+ # Span: recurse on element types directly so that readonly propagates into T.
900
+ # Span[T] accepts both Span[X] (T=X) and Span[readonly[X]] (T=readonly[X]).
901
+ # Done explicitly (not via the generic NominalType branch) so that readonly
902
+ # and _match_record_with_inference would reject by name if it treated spans differently.
903
+ if is_span(param_type):
904
+ if is_span(arg_type):
905
+ return self.match_type_with_inference(
906
+ param_type.type_args[0], arg_type.type_args[0], inferred
907
+ )
908
+ return False
909
+
910
+ # NominalType (record) with type args (e.g., Box[T] nested)
911
+ if isinstance(param_type, NominalType) and param_type.is_record and param_type.type_args:
912
+ return self._match_record_with_inference(param_type, arg_type, inferred)
913
+
914
+ # (PtrType handled above, before list)
915
+
916
+ # Optional[T] -- unwrap and recurse (bare T can coerce to Optional[T])
917
+ if isinstance(param_type, OptionalType):
918
+ inner_arg = arg_type.inner if isinstance(arg_type, OptionalType) else arg_type
919
+ return self.match_type_with_inference(
920
+ param_type.inner, inner_arg, inferred
921
+ )
922
+
923
+ # CallableType with TypeParamRef in param/return (e.g. Fn[[T], U] or Callable[[T], U])
924
+ if is_callable_type(param_type):
925
+ if not is_callable_type(arg_type):
926
+ return False
927
+ if len(param_type.param_types) != len(arg_type.param_types):
928
+ return False
929
+ for pp, ap in zip(param_type.param_types, arg_type.param_types):
930
+ if not self.match_type_with_inference(pp, ap, inferred):
931
+ return False
932
+ return self.match_type_with_inference(
933
+ param_type.return_type, arg_type.return_type, inferred)
934
+
935
+ # Own[T] wrapper -- unwrap and match T against arg (any type can be owned).
936
+ # Also strip readonly: readonly[T] passed to Own[T] is an implicit copy.
937
+ if isinstance(param_type, OwnType):
938
+ inner_arg = arg_type.wrapped if isinstance(arg_type, OwnType) else arg_type
939
+ if isinstance(inner_arg, ReadonlyType):
940
+ inner_arg = inner_arg.wrapped
941
+ return self.match_type_with_inference(
942
+ param_type.wrapped, inner_arg, inferred
943
+ )
944
+
945
+ # Generic recursive alias instance (e.g. Tree[T]): match alias identity,
946
+ # then recurse on type args to infer the params.
947
+ if isinstance(param_type, RecursiveAliasInstanceType):
948
+ if (not isinstance(arg_type, RecursiveAliasInstanceType)
949
+ or param_type.qname != arg_type.qname
950
+ or len(param_type.type_args) != len(arg_type.type_args)):
951
+ return False
952
+ return all(
953
+ self.match_type_with_inference(p, a, inferred)
954
+ for p, a in zip(param_type.type_args, arg_type.type_args)
955
+ )
956
+
957
+ # None literal (NoneType) matches None annotation (VoidType)
958
+ if isinstance(param_type, VoidType) and isinstance(arg_type, NoneType):
959
+ return True
960
+
961
+ # Concrete type -- check compatibility
962
+ if self.types_match_for_inference(param_type, arg_type):
963
+ return True
964
+
965
+ # For concrete parameters in generic inference, allow the same
966
+ # argument coercions used by normal call checking (e.g. Int32 -> Int64).
967
+ arg_unwrapped = unwrap_readonly(arg_type)
968
+ return resolve_coercion(arg_unwrapped, param_type, CoercionContext.ARG) is not None
969
+
970
+ def _match_protocol_type_args_with_inference(
971
+ self,
972
+ param_type: TpyType,
973
+ arg_type: TpyType,
974
+ inferred: dict[str, TpyType],
975
+ ) -> bool:
976
+ """Match a protocol with TypeParamRef type_args against arg_type (e.g., NativeIterable[T], Iterator[T])."""
977
+ # Mirror classify_protocol_conformance: conformance is on the
978
+ # underlying record, not its Ref/readonly/Own wrapper.
979
+ arg_type = unwrap_qualifiers(arg_type)
980
+ # GenExprType satisfies Iterable[T] and Iterator[T];
981
+ # CopyIter/OwnIter satisfy Iterable[T] only.
982
+ if ((isinstance(arg_type, GenExprType) and param_type.qualified_name() in (qnames.ITERABLE, qnames.ITERATOR))
983
+ or ((is_copy_iter(arg_type) or is_own_iter(arg_type)) and param_type.qualified_name() == qnames.ITERABLE)):
984
+ if len(param_type.type_args) == 1:
985
+ elem = arg_type.element_type if isinstance(arg_type, GenExprType) else arg_type.type_args[0]
986
+ return self.match_type_with_inference(param_type.type_args[0], elem, inferred)
987
+ return True
988
+
989
+ protocol_name = param_type.name
990
+ if param_type.qualified_name() == "typing.Iterator":
991
+ if is_protocol_type(arg_type) and arg_type.qualified_name() == "typing.Iterator" and arg_type.type_args:
992
+ elem_type = arg_type.type_args[0]
993
+ else:
994
+ elem_type = builtin_modules.get_extends_protocol_type_arg(
995
+ arg_type, protocol_name, registry=self.ctx.registry)
996
+ if elem_type is None:
997
+ elem_type = self._infer_protocol_type_arg_structurally(
998
+ arg_type, protocol_name)
999
+ else:
1000
+ # General path: direct protocol match, extends declarations,
1001
+ # then structural inference from method signatures.
1002
+ if is_protocol_type(arg_type) and arg_type.qualified_name() == param_type.qualified_name() and arg_type.type_args:
1003
+ elem_type = arg_type.type_args[0]
1004
+ else:
1005
+ elem_type = builtin_modules.get_extends_protocol_type_arg(
1006
+ arg_type, protocol_name, registry=self.ctx.registry)
1007
+ if elem_type is None:
1008
+ elem_type = self._infer_protocol_type_arg_structurally(
1009
+ arg_type, protocol_name)
1010
+ if elem_type is None:
1011
+ return False
1012
+ # Single type_arg: use recursive matching to handle compound types
1013
+ # like NativeIterable[tuple[K, V]] where the type_arg is a TupleType
1014
+ if len(param_type.type_args) == 1:
1015
+ return self.match_type_with_inference(param_type.type_args[0], elem_type, inferred)
1016
+ for ta in param_type.type_args:
1017
+ if isinstance(ta, TypeParamRef):
1018
+ if ta.name in inferred:
1019
+ if not self.types_match_for_inference(inferred[ta.name], elem_type):
1020
+ return False
1021
+ else:
1022
+ inferred[ta.name] = elem_type
1023
+ return True
1024
+
1025
+ def _infer_protocol_type_arg_structurally(
1026
+ self, arg_type: TpyType, protocol_name: str,
1027
+ ) -> TpyType | None:
1028
+ """Infer a protocol's type arg by matching method signatures structurally.
1029
+
1030
+ Uses match_type_with_inference to unify protocol method signatures
1031
+ (containing TypeParamRefs like T) against the record's concrete method
1032
+ signatures. Handles all compound return/param types (Span, Optional, etc.).
1033
+
1034
+ Also handles protocol-to-protocol inference: when arg_type is a protocol
1035
+ (e.g. Iterator[Int32]) matching a different protocol (e.g. Iterable[T]),
1036
+ looks up the arg protocol's method signatures and matches them.
1037
+ """
1038
+ protocol_info = self.ctx.registry.scan_by_short_name(protocol_name)
1039
+ if protocol_info is None or not protocol_info.type_params:
1040
+ return None
1041
+
1042
+ # Protocol-to-protocol: arg is a protocol with concrete type args
1043
+ if is_protocol_type(arg_type) and isinstance(arg_type, NominalType) and arg_type.type_args:
1044
+ arg_protocol_info = protocol_info_of(arg_type)
1045
+ if arg_protocol_info is not None:
1046
+ return self._infer_protocol_type_arg_from_protocol(
1047
+ arg_type, arg_protocol_info, protocol_info)
1048
+
1049
+ record = self.ctx.registry.get_record_for_type(arg_type)
1050
+ if record is None:
1051
+ return None
1052
+
1053
+ # NominalType carries type_args directly; pending containers
1054
+ # (PendingListType etc.) need extract_type_params to surface them.
1055
+ type_subst: dict[str, TpyType] = {}
1056
+ if record.type_params:
1057
+ if isinstance(arg_type, NominalType) and arg_type.type_args:
1058
+ type_subst = dict(zip(record.type_params, arg_type.type_args))
1059
+ else:
1060
+ extracted = builtin_modules.extract_type_params(arg_type)
1061
+ type_subst = {tp: extracted[tp] for tp in record.type_params if tp in extracted}
1062
+
1063
+ def _substitute(t: TpyType) -> TpyType:
1064
+ # Propagate the record's class type params into nested
1065
+ # positions: e.g. for Future[Int32].poll's return type
1066
+ # `Poll[T]`, we want `Poll[Int32]`. Recursing here matters
1067
+ # for any generic record whose protocol-conforming method
1068
+ # returns or accepts a compound type wrapping the class T.
1069
+ t = unwrap_ref_type(t)
1070
+ if not type_subst:
1071
+ return t
1072
+ return self.substitute_types(t, type_subst)
1073
+
1074
+ inferred: dict[str, TpyType] = {}
1075
+ for method_sig in protocol_info.methods:
1076
+ for method_info in self.ctx.registry.get_method_overloads_with_parents(record, method_sig.name):
1077
+ if len(method_info.params) != len(method_sig.params):
1078
+ continue
1079
+ ret = _substitute(method_info.return_type)
1080
+ self.match_type_with_inference(method_sig.return_type, ret, inferred)
1081
+ for (_, proto_ptype), (_, record_ptype) in zip(
1082
+ method_sig.params, method_info.params
1083
+ ):
1084
+ self.match_type_with_inference(
1085
+ proto_ptype, _substitute(record_ptype), inferred)
1086
+ break
1087
+
1088
+ first_param = protocol_info.type_params[0]
1089
+ return inferred.get(first_param)
1090
+
1091
+ def _infer_protocol_type_arg_from_protocol(
1092
+ self,
1093
+ arg_type: NominalType,
1094
+ arg_protocol: 'ProtocolInfo',
1095
+ target_protocol: 'ProtocolInfo',
1096
+ ) -> TpyType | None:
1097
+ """Infer target protocol's type arg from an arg protocol's method signatures.
1098
+
1099
+ E.g. Iterator[Int32] matching Iterable[T]: looks up Iterator's __iter__
1100
+ method (returns Self = Iterator[Int32]), matches against Iterable's
1101
+ __iter__ (returns Iterator[T]) to infer T = Int32.
1102
+ """
1103
+ # Build substitution for arg protocol: resolve Self and type params
1104
+ arg_subst: dict[str, TpyType] = {"Self": arg_type}
1105
+ if arg_protocol.type_params and arg_type.type_args:
1106
+ arg_subst.update(dict(zip(arg_protocol.type_params, arg_type.type_args)))
1107
+
1108
+ inferred: dict[str, TpyType] = {}
1109
+ for target_method in target_protocol.methods:
1110
+ for arg_method in arg_protocol.methods:
1111
+ if arg_method.name != target_method.name:
1112
+ continue
1113
+ if len(arg_method.params) != len(target_method.params):
1114
+ continue
1115
+ # Substitute arg protocol's type params + Self
1116
+ resolved_ret = self.substitute_types(arg_method.return_type, arg_subst)
1117
+ self.match_type_with_inference(
1118
+ target_method.return_type, resolved_ret, inferred)
1119
+ for (_, target_ptype), (_, arg_ptype) in zip(
1120
+ target_method.params, arg_method.params
1121
+ ):
1122
+ resolved_ptype = self.substitute_types(arg_ptype, arg_subst)
1123
+ self.match_type_with_inference(
1124
+ target_ptype, resolved_ptype, inferred)
1125
+ break
1126
+
1127
+ # Returns only the first type param -- callers handle multi-param
1128
+ # protocols via match_type_with_inference on the full type_args tuple.
1129
+ first_param = target_protocol.type_params[0]
1130
+ return inferred.get(first_param)
1131
+
1132
+ def _match_array_with_inference(
1133
+ self,
1134
+ param_type: NominalType,
1135
+ arg_type: TpyType,
1136
+ inferred: dict[str, TpyType],
1137
+ ) -> bool:
1138
+ """Match Array with TypeParamRef element or size (e.g., Array[T, N])."""
1139
+ if not is_array(arg_type):
1140
+ return False
1141
+ p_elem, p_size = param_type.type_args[0], param_type.type_args[1]
1142
+ a_elem, a_size = arg_type.type_args[0], arg_type.type_args[1]
1143
+ if not self.match_type_with_inference(p_elem, a_elem, inferred):
1144
+ return False
1145
+ # Match sizes
1146
+ if isinstance(p_size, TypeParamRef):
1147
+ if isinstance(a_size, int):
1148
+ param_name = p_size.name
1149
+ if param_name in inferred:
1150
+ if inferred[param_name] != a_size:
1151
+ return False
1152
+ else:
1153
+ inferred[param_name] = a_size
1154
+ return True
1155
+ if isinstance(a_size, TypeParamRef):
1156
+ return p_size.name == a_size.name
1157
+ return False
1158
+ return p_size == a_size
1159
+
1160
+ def _match_record_with_inference(
1161
+ self,
1162
+ param_type: NominalType,
1163
+ arg_type: TpyType,
1164
+ inferred: dict[str, TpyType],
1165
+ ) -> bool:
1166
+ """Match a generic record NominalType against arg_type (e.g., Box[T])."""
1167
+ if not (isinstance(arg_type, NominalType) and arg_type.is_record and arg_type.name == param_type.name):
1168
+ return False
1169
+ if len(param_type.type_args) != len(arg_type.type_args):
1170
+ return False
1171
+ for pt, at in zip(param_type.type_args, arg_type.type_args):
1172
+ if isinstance(pt, TpyType) and isinstance(at, TpyType):
1173
+ if not self.match_type_with_inference(pt, at, inferred):
1174
+ return False
1175
+ elif isinstance(pt, TypeParamRef) and pt.kind == TypeParamKind.INT and isinstance(at, int):
1176
+ if pt.name in inferred:
1177
+ if inferred[pt.name] != at:
1178
+ return False
1179
+ else:
1180
+ inferred[pt.name] = at
1181
+ elif isinstance(pt, int) and isinstance(at, int):
1182
+ if pt != at:
1183
+ return False
1184
+ elif isinstance(pt, TypeParamRef) and isinstance(at, TypeParamRef):
1185
+ if pt.name != at.name:
1186
+ return False
1187
+ else:
1188
+ return False
1189
+ return True
1190
+
1191
+ def types_match_for_inference(self, type_a: TpyType, type_b: TpyType) -> bool:
1192
+ """Check if two types match for inference consistency.
1193
+
1194
+ Recurses element-wise through tuple/list/dict/set so that the
1195
+ literal-coercion rules below apply inside compound shapes -- not
1196
+ just at the top level.
1197
+ """
1198
+ if type_a == type_b:
1199
+ return True
1200
+ if isinstance(type_a, IntLiteralType) and is_integer_type(type_b):
1201
+ return True
1202
+ if isinstance(type_b, IntLiteralType) and is_integer_type(type_a):
1203
+ return True
1204
+ if isinstance(type_a, IntLiteralType) and isinstance(type_b, IntLiteralType):
1205
+ return True
1206
+ if isinstance(type_a, FloatLiteralType) and is_float_type(type_b):
1207
+ return True
1208
+ if isinstance(type_b, FloatLiteralType) and is_float_type(type_a):
1209
+ return True
1210
+ if isinstance(type_a, FloatLiteralType) and isinstance(type_b, FloatLiteralType):
1211
+ return True
1212
+ if isinstance(type_a, PendingViewType) and isinstance(type_b, PendingViewType):
1213
+ return type_a.family is type_b.family
1214
+ if isinstance(type_a, PendingViewType):
1215
+ return type_b in (type_a.family.owned_type, type_a.family.view_type)
1216
+ if isinstance(type_b, PendingViewType):
1217
+ return type_a in (type_b.family.owned_type, type_b.family.view_type)
1218
+ if isinstance(type_a, TupleType) and isinstance(type_b, TupleType):
1219
+ if len(type_a.element_types) != len(type_b.element_types):
1220
+ return False
1221
+ return all(self.types_match_for_inference(a, b)
1222
+ for a, b in zip(type_a.element_types, type_b.element_types))
1223
+ if is_list(type_a) and is_list(type_b):
1224
+ return self.types_match_for_inference(type_a.type_args[0], type_b.type_args[0])
1225
+ if is_dict(type_a) and is_dict(type_b):
1226
+ return (self.types_match_for_inference(type_a.type_args[0], type_b.type_args[0])
1227
+ and self.types_match_for_inference(type_a.type_args[1], type_b.type_args[1]))
1228
+ if is_set(type_a) and is_set(type_b):
1229
+ return self.types_match_for_inference(type_a.type_args[0], type_b.type_args[0])
1230
+ return False
1231
+
1232
+ def infer_type_params_for_function(
1233
+ self,
1234
+ func: FunctionInfo,
1235
+ arg_types: list[TpyType],
1236
+ type_conforms_to_protocol: callable,
1237
+ expected_return_type: TpyType | None = None,
1238
+ explicit_type_args: 'tuple[TpyType | None, ...] | None' = None,
1239
+ ) -> dict[str, TpyType] | None:
1240
+ """Infer type parameters from function arguments.
1241
+
1242
+ Returns dict of inferred type params (e.g., {"T": Int32}) on success, None on failure.
1243
+ If expected_return_type is provided, unresolved params are matched against the return type.
1244
+ If explicit_type_args is provided, pre-populates inferred with those (positional).
1245
+ """
1246
+ if len(arg_types) < func.min_args or len(arg_types) > func.max_args:
1247
+ return None
1248
+
1249
+ inferred: dict[str, TpyType] = {}
1250
+ if explicit_type_args:
1251
+ if len(explicit_type_args) > len(func.type_params):
1252
+ return None
1253
+ for tp, arg in zip(func.type_params, explicit_type_args):
1254
+ if arg is not None: # None = _ wildcard, skip
1255
+ inferred[tp] = arg
1256
+ arg_idx = 0
1257
+ for p in func.params:
1258
+ if arg_idx >= len(arg_types):
1259
+ break
1260
+ ptype = p.type
1261
+ if p.is_variadic and is_span(unwrap_ref_type(ptype)):
1262
+ # Variadic param: match each remaining arg against element type.
1263
+ # `elem_type` is bare T (the Span[T] wrapping was stripped), so
1264
+ # strip Ref from the arg side too -- otherwise a non-value arg
1265
+ # whose analyzed type is `Ref[Box]` (e.g. a borrowed param)
1266
+ # binds T=Ref[Box], which renders as illegal `varargs<Box&>`.
1267
+ # The non-variadic ref-param path gets this via the Ref-vs-Ref
1268
+ # strip in match_type_with_inference; varargs use bare-T params
1269
+ # (`varargs<T>`, no val_or_ref indirection) so they need the
1270
+ # equivalent strip explicitly here.
1271
+ elem_type = unwrap_readonly(unwrap_ref_type(ptype).type_args[0])
1272
+ while arg_idx < len(arg_types):
1273
+ arg_t = unwrap_ref_type(arg_types[arg_idx])
1274
+ if not self.match_type_with_inference(elem_type, arg_t, inferred):
1275
+ return None
1276
+ arg_idx += 1
1277
+ continue
1278
+ if p.keyword_only:
1279
+ # Skip keyword-only params in arg_types matching (they were
1280
+ # appended by resolve_kwargs, not from positional args)
1281
+ continue
1282
+ # @value_ptr_coercion: Ptr[T] params accept T values, so match
1283
+ # the arg against the pointee type for inference purposes.
1284
+ match_type = ptype
1285
+ if func.value_ptr_coercion and isinstance(ptype, PtrType):
1286
+ match_type = ptype.pointee
1287
+ if not self.match_type_with_inference(match_type, arg_types[arg_idx], inferred):
1288
+ return None
1289
+ arg_idx += 1
1290
+
1291
+ # Fallback: infer remaining params from expected return type
1292
+ if expected_return_type is not None:
1293
+ unresolved = [tp for tp in func.type_params if tp not in inferred]
1294
+ if unresolved:
1295
+ ret = func.return_type.wrapped if isinstance(func.return_type, OwnType) else func.return_type
1296
+ exp = expected_return_type.wrapped if isinstance(expected_return_type, OwnType) else expected_return_type
1297
+ self.match_type_with_inference(ret, exp, inferred)
1298
+
1299
+ # LHS-hint @dynamic-protocol preference for function-call inference;
1300
+ # mirrors the equivalent block in `infer_type_params_for_record`.
1301
+ if expected_return_type is not None and func.return_type is not None:
1302
+ self._apply_lhs_hint_to_function_return(
1303
+ func.return_type, expected_return_type, inferred
1304
+ )
1305
+
1306
+ # Resolve pending types for codegen.
1307
+ for k, v in list(inferred.items()):
1308
+ if isinstance(v, IntLiteralType):
1309
+ inferred[k] = self.ctx.default_int_for_literal(v)
1310
+ elif isinstance(v, PendingListType):
1311
+ # Resolve PendingListType to list
1312
+ elem_type = v.element_type
1313
+ if isinstance(elem_type, IntLiteralType):
1314
+ elem_type = self.ctx.default_int_for_literal(elem_type)
1315
+ inferred[k] = make_list(elem_type)
1316
+
1317
+ # An empty container literal pins T to UnknownElementType but
1318
+ # carries no real evidence -- treat it like an absent arg so the
1319
+ # @type_param_default fallback applies.
1320
+ if func.type_param_defaults:
1321
+ for tp in func.type_params:
1322
+ if tp not in func.type_param_defaults:
1323
+ continue
1324
+ current = inferred.get(tp)
1325
+ if current is not None and not isinstance(current, UnknownElementType):
1326
+ continue
1327
+ sentinel = func.type_param_defaults[tp]
1328
+ if sentinel == qnames.DEFAULT_INT:
1329
+ inferred[tp] = self.ctx.default_int_type
1330
+ else:
1331
+ raise ValueError(f"Unknown type_param_default sentinel: {sentinel!r}")
1332
+
1333
+ # `U: T` with U arg-inferred but T unconstrained: default T to U.
1334
+ # Sound because U trivially satisfies its own bound when used as T,
1335
+ # and gives hint-free bounded-factory calls (`x = f(v)` where f is
1336
+ # `def f[U: T](v: Own[U]) -> Own[Box[T]]`) a defined inference
1337
+ # outcome instead of "Cannot infer T". Multi-step chains
1338
+ # (`V: U`, `U: T`) iterate to a fixed point.
1339
+ if func.type_param_bounds:
1340
+ changed = True
1341
+ while changed:
1342
+ changed = False
1343
+ for u_name, bound in func.type_param_bounds.items():
1344
+ if not isinstance(bound, TypeParamRef):
1345
+ continue
1346
+ t_name = bound.name
1347
+ if t_name in inferred:
1348
+ continue
1349
+ src = inferred.get(u_name)
1350
+ if src is not None and not isinstance(src, UnknownElementType):
1351
+ inferred[t_name] = src
1352
+ changed = True
1353
+
1354
+ # Reject leftover UnknownElementType: letting it ride through
1355
+ # substituted param types surfaces later as the indirect
1356
+ # pending-list resolver error; failing here lets the caller emit
1357
+ # a clean "Cannot infer type arguments" diagnostic instead.
1358
+ for tp in func.type_params:
1359
+ if tp not in inferred:
1360
+ return None
1361
+ if isinstance(inferred[tp], UnknownElementType):
1362
+ return None
1363
+
1364
+ # Substitute already-inferred params into the bound before validating
1365
+ # so a bound that names another param (`U: T`) is checked against the
1366
+ # resolved form, not the raw type-param.
1367
+ for param_name, type_arg in inferred.items():
1368
+ if param_name in func.type_param_bounds:
1369
+ bound = self.substitute_type_params(func.type_param_bounds[param_name], inferred)
1370
+ if not type_conforms_to_protocol(type_arg, bound):
1371
+ # Return None to signal inference failure (allows overload resolution to try other candidates)
1372
+ return None
1373
+
1374
+ return inferred
1375
+
1376
+ def infer_type_params_for_record(
1377
+ self,
1378
+ record: RecordInfo,
1379
+ arg_types: list[TpyType],
1380
+ expected_type: TpyType | None = None,
1381
+ explicit_type_args: tuple['TpyType | None', ...] | None = None,
1382
+ ) -> dict[str, TpyType] | None:
1383
+ """Infer type parameters from constructor arguments for user-defined generic record.
1384
+
1385
+ Returns dict of inferred type params (e.g., {"T": Int32}) on success, None on failure.
1386
+ If expected_type is provided, unresolved params are matched against the record type pattern.
1387
+ If explicit_type_args is provided, pre-populates inferred with those (positional, None = skip).
1388
+ """
1389
+ min_args = sum(1 for _, _, default in record.init_params if default is None)
1390
+ if len(arg_types) < min_args or len(arg_types) > len(record.init_params):
1391
+ return None
1392
+
1393
+ inferred: dict[str, TpyType] = {}
1394
+ if explicit_type_args:
1395
+ if len(explicit_type_args) > len(record.type_params):
1396
+ return None
1397
+ for tp, arg in zip(record.type_params, explicit_type_args):
1398
+ if arg is not None:
1399
+ inferred[tp] = arg
1400
+ for (pname, ptype, _), arg_type in zip(record.init_params, arg_types):
1401
+ if not self.match_type_with_inference(ptype, arg_type, inferred):
1402
+ return None
1403
+
1404
+ # Fallback: infer remaining params from expected type
1405
+ if expected_type is not None:
1406
+ unresolved = [tp for tp in record.type_params if tp not in inferred]
1407
+ if unresolved:
1408
+ exp = expected_type.wrapped if isinstance(expected_type, OwnType) else expected_type
1409
+ record_pattern = NominalType(record.name, tuple(TypeParamRef(tp) for tp in record.type_params),
1410
+ _module_qname=record.qualified_name())
1411
+ self.match_type_with_inference(record_pattern, exp, inferred)
1412
+
1413
+ # Verify all type params were inferred
1414
+ for tp in record.type_params:
1415
+ if tp not in inferred:
1416
+ return None
1417
+
1418
+ # LHS-hint @dynamic-protocol preference (structural conformer -> Adapter
1419
+ # wrap at call site). See `_apply_dyn_hint_at_position` for the gate.
1420
+ if expected_type is not None:
1421
+ exp = unwrap_qualifiers(expected_type)
1422
+ if (isinstance(exp, NominalType)
1423
+ and exp.qualified_name() == record.qualified_name()
1424
+ and len(exp.type_args) == len(record.type_params)):
1425
+ for tp, hint_t in zip(record.type_params, exp.type_args):
1426
+ self._apply_dyn_hint_at_position(tp, hint_t, inferred)
1427
+
1428
+ return inferred
1429
+
1430
+ def _apply_dyn_hint_at_position(
1431
+ self, tp_name: str, hint_t: TpyType, inferred: dict[str, TpyType]
1432
+ ) -> None:
1433
+ # Skip if the inferred concrete inherits the hint protocol -- Covariant[T]
1434
+ # uplift handles it. Otherwise generic records that allocate with raw
1435
+ # unsafe_alloc (e.g. Tagged in covariant_custom) would break since
1436
+ # unsafe_alloc can't allocate sizeof(abstract_base).
1437
+ if tp_name not in inferred:
1438
+ return
1439
+ if not (isinstance(hint_t, NominalType) and is_dyn_protocol(hint_t)):
1440
+ return
1441
+ inferred_t = inferred[tp_name]
1442
+ if isinstance(inferred_t, NominalType) and is_dyn_protocol(inferred_t):
1443
+ return
1444
+ if self._inherits_protocol(inferred_t, hint_t):
1445
+ return
1446
+ inferred[tp_name] = hint_t
1447
+
1448
+ def _apply_lhs_hint_to_function_return(
1449
+ self, ret_pattern: TpyType, hint: TpyType, inferred: dict[str, TpyType]
1450
+ ) -> None:
1451
+ # Recurse so nested generics (e.g. `Own[List[Wrapper[T]]]`) get the
1452
+ # switch too, not just the top-level wrapper.
1453
+ ret_pattern = unwrap_qualifiers(ret_pattern)
1454
+ hint = unwrap_qualifiers(hint)
1455
+ if not (isinstance(ret_pattern, NominalType) and isinstance(hint, NominalType)):
1456
+ return
1457
+ if ret_pattern.qualified_name() != hint.qualified_name():
1458
+ return
1459
+ if len(ret_pattern.type_args) != len(hint.type_args):
1460
+ return
1461
+ for r_arg, h_arg in zip(ret_pattern.type_args, hint.type_args):
1462
+ if isinstance(r_arg, TypeParamRef):
1463
+ self._apply_dyn_hint_at_position(r_arg.name, h_arg, inferred)
1464
+ else:
1465
+ self._apply_lhs_hint_to_function_return(r_arg, h_arg, inferred)
1466
+
1467
+ def _seed_subst(
1468
+ self, pattern: TpyType, hint: TpyType,
1469
+ ) -> dict[str, TpyType]:
1470
+ """Match a TPRef-bearing ``pattern`` against ``hint`` and return the
1471
+ useful inferred bindings.
1472
+
1473
+ Internal helper shared by ``seed_subst_from_return_hint`` (function /
1474
+ method calls; pattern = ``func.return_type``) and
1475
+ ``seed_subst_from_record_pattern`` (record constructors; pattern =
1476
+ ``NominalType(record.name, [TypeParamRef(tp) for tp ...])``).
1477
+
1478
+ Strips Own/Readonly/Ref qualifiers from both sides (mirrors
1479
+ ``_apply_lhs_hint_to_function_return``) so a ``readonly[Container[T]]``
1480
+ LHS hint or an ``Own[Container[T]]`` return type still seeds correctly.
1481
+ ``match_type_with_inference`` mutates its accumulator even on branches
1482
+ that ultimately return False, so the match runs into a temporary dict
1483
+ and the bindings are committed only on overall success -- prevents a
1484
+ structurally-shaped but inner-mismatched LHS hint from leaking partial
1485
+ seed bindings.
1486
+ """
1487
+ p = unwrap_qualifiers(pattern)
1488
+ h = unwrap_qualifiers(hint)
1489
+ tmp: dict[str, TpyType] = {}
1490
+ if not self.match_type_with_inference(p, h, tmp):
1491
+ return {}
1492
+ return {k: v for k, v in tmp.items() if _is_useful_seed_binding(v)}
1493
+
1494
+ def compute_representational_subst_params(
1495
+ self, fi: FunctionInfo, inferred_type_args: tuple[TpyType, ...],
1496
+ ) -> 'frozenset[str] | None':
1497
+ """Decide which marked type-params need adapter substitution at codegen.
1498
+
1499
+ Reads ``fi.root.representational_type_params`` (set by sema body
1500
+ analysis when ``Ptr[U] -> Ptr[T]`` coerces in the callee body) and,
1501
+ for each marked U, returns U iff its substituted bound is a @dynamic
1502
+ protocol structurally satisfied by the inferred U (i.e., the concrete
1503
+ u_sub does NOT C++-inherit the protocol -- the Adapter wrap is then
1504
+ required so the body's pointer upcast becomes valid).
1505
+
1506
+ Result is stored on the call AST node
1507
+ (``TpyCall.representational_subst_params`` /
1508
+ ``TpyMethodCall.representational_subst_params``) so codegen reads
1509
+ the decision instead of re-deriving it across multiple emission sites.
1510
+ Returns ``None`` when no marked param needs the adapter detour.
1511
+ """
1512
+ canonical = fi.root
1513
+ marked = canonical.representational_type_params
1514
+ if not marked or not inferred_type_args or not fi.type_params:
1515
+ return None
1516
+ subst = dict(zip(fi.type_params, inferred_type_args))
1517
+ result: set[str] = set()
1518
+ for tp_name in fi.type_params:
1519
+ if tp_name not in marked:
1520
+ continue
1521
+ bound = canonical.type_param_bounds.get(tp_name)
1522
+ if bound is None:
1523
+ continue
1524
+ t_sub = self.substitute_type_params(bound, subst)
1525
+ if not is_dyn_protocol(t_sub):
1526
+ continue
1527
+ u_sub = subst[tp_name]
1528
+ if not isinstance(u_sub, NominalType) or not u_sub.is_user_record:
1529
+ continue
1530
+ if self.protocols.directly_implements_dynamic(u_sub, t_sub):
1531
+ continue
1532
+ result.add(tp_name)
1533
+ return frozenset(result) if result else None
1534
+
1535
+ def seed_subst_from_return_hint(
1536
+ self, func: FunctionInfo, expected_return_type: TpyType | None,
1537
+ ) -> dict[str, TpyType]:
1538
+ """Pre-bind type params by matching ``expected_return_type`` against
1539
+ ``func.return_type``. Used to give nested generic call args a hint
1540
+ that reflects the LHS-derived outer type before arg-driven inference
1541
+ has any evidence to contribute.
1542
+
1543
+ For each unbound method-local type param `U` with a bound `B`, if
1544
+ substituting the seed into `B` yields a concrete type, default `U =
1545
+ B[seed]`. U=B trivially satisfies the bound (B <: B); the arg-driven
1546
+ inference may later refine U to a more specific subtype of B.
1547
+ Without this, a bounded-factory call with a nested generic arg --
1548
+ `f[U: T](v: Own[U])` called with `f(Box(Cat(...)))` against an LHS
1549
+ `Box[Greeter]` -- would analyze the inner arg with no type-arg hint
1550
+ and infer `U=Box[Cat]` instead of `Box[Greeter]`.
1551
+ """
1552
+ if expected_return_type is None or func.return_type is None:
1553
+ return {}
1554
+ if not func.type_params:
1555
+ return {}
1556
+ seed = self._seed_subst(func.return_type, expected_return_type)
1557
+ if seed and func.type_param_bounds:
1558
+ for tp in func.type_params:
1559
+ if tp in seed:
1560
+ continue
1561
+ bound = func.type_param_bounds.get(tp)
1562
+ if bound is None:
1563
+ continue
1564
+ substituted = self.substitute_type_params(bound, seed)
1565
+ if not contains_type_param(substituted):
1566
+ seed[tp] = substituted
1567
+ return seed
1568
+
1569
+ def seed_subst_from_record_pattern(
1570
+ self, record: RecordInfo, expected_type: TpyType | None,
1571
+ ) -> dict[str, TpyType]:
1572
+ """Pre-bind type params by matching ``expected_type`` against a
1573
+ ``NominalType(record.name, [TypeParamRef(tp) ...])`` pattern.
1574
+
1575
+ Record-constructor analogue of ``seed_subst_from_return_hint``: gives
1576
+ constructor args a contextual hint reflecting the LHS-derived outer
1577
+ type before arg-driven inference has any evidence to contribute.
1578
+ Lets nested generic chains like ``Rc.new(Box(Box(Dog(...))))`` resolve
1579
+ through the inner Box's args, not just the outer Rc.new's.
1580
+ """
1581
+ if expected_type is None or not record.type_params:
1582
+ return {}
1583
+ # Carry kind + bound on each TPRef so the seed pattern matches the
1584
+ # record's actual type-param shape: INT-kind params (e.g. Array's N)
1585
+ # pair against integer values in ``_match_array_with_inference``, and
1586
+ # bounded type params surface the bound for downstream consumers
1587
+ # that inspect TPRef.bound.
1588
+ type_param_kinds = record.type_param_kinds or [TypeParamKind.TYPE] * len(record.type_params)
1589
+ pattern_args: tuple[TpyType, ...] = tuple(
1590
+ TypeParamRef(
1591
+ tp,
1592
+ bound=record.type_param_bounds.get(tp),
1593
+ kind=type_param_kinds[i],
1594
+ )
1595
+ for i, tp in enumerate(record.type_params)
1596
+ )
1597
+ pattern = NominalType(
1598
+ record.name,
1599
+ pattern_args,
1600
+ _module_qname=record.qualified_name(),
1601
+ )
1602
+ return self._seed_subst(pattern, expected_type)
1603
+
1604
+ def candidate_arg_hints(
1605
+ self, func: FunctionInfo, n_args: int, lhs_hint: TpyType | None,
1606
+ ) -> list[TpyType | None] | None:
1607
+ """Per-arg ``expr_type_hint`` from one overload candidate + the LHS hint.
1608
+
1609
+ Probe-time helper for overload pre-analysis: lets nested generic-call
1610
+ and record-ctor args see a hint on their first analysis pass, so the
1611
+ cache short-circuit at the top of ``_analyze_generic_function_call`` /
1612
+ ``_analyze_record_constructor`` doesn't lock in a hint-naive type that
1613
+ the post-selection retry can't refresh.
1614
+
1615
+ Returns:
1616
+ - ``None`` when the candidate's return shape does NOT match ``lhs_hint``.
1617
+ Callers exclude these candidates from the LHS-matching count.
1618
+ - a list of length ``n_args`` when the candidate matches. Per-position
1619
+ entries may still be ``None`` when no useful hint can be derived
1620
+ (e.g. a generic candidate whose seed binds only return-type-only
1621
+ TPRefs, leaving every param's hint reduced via ``post_substitute_hint``
1622
+ to None).
1623
+
1624
+ This split lets ``_probe_analyze_args`` / ``_probe_analyze_method_args``
1625
+ distinguish "doesn't LHS-match" from "matches but has no useful per-arg
1626
+ hint" -- a conflation that would otherwise let a non-contributing
1627
+ LHS-matching candidate slip past the single-LHS-matching gate and
1628
+ leave the seed candidate's hint cached against a different winner.
1629
+ """
1630
+ if lhs_hint is None:
1631
+ return None
1632
+ # Arity gate: skip candidates that can't actually accept ``n_args``
1633
+ # positional args. Otherwise an arity-mismatched-but-LHS-matching
1634
+ # candidate could be the sole contributor in the probe's
1635
+ # single-LHS-matching gate -- biasing arg analysis toward a
1636
+ # candidate that ``resolve_overload`` will reject anyway.
1637
+ # Conservative direction: ``func.max_args`` includes keyword-only
1638
+ # params (it's ``len(self.params)``), while ``n_args`` is positional
1639
+ # only. The gate may over-admit a candidate with required keyword-
1640
+ # only params -- safe because ``resolve_overload`` still catches it,
1641
+ # the probe just wastes work building a hint that ends up unused.
1642
+ if n_args > func.max_args or n_args < func.min_args:
1643
+ return None
1644
+ if func.type_params:
1645
+ seed = self.seed_subst_from_return_hint(func, lhs_hint)
1646
+ if not seed:
1647
+ return None
1648
+ return [seeded_arg_hint(func.params, i, seed) for i in range(n_args)]
1649
+ # Non-generic / substituted candidate. Gate on a structural match
1650
+ # between return type and LHS hint so we only bias toward LHS-aligned
1651
+ # overloads. Direction: ``lhs_hint`` is param-side, ``return_type`` is
1652
+ # arg-side -- asks "can the return fit into the LHS slot?". The
1653
+ # opposite direction is wrong because ``match_type_with_inference``
1654
+ # only unwraps ``Optional[T]`` on the param side, which would falsely
1655
+ # accept a candidate returning ``Optional[Foo]`` as matching
1656
+ # ``lhs_hint = Foo`` and bias arg analysis toward the wrong overload.
1657
+ if func.return_type is None:
1658
+ return None
1659
+ # Guard: when ``lhs_hint`` contains a ``TypeParamRef`` (e.g. the call
1660
+ # site is inside a generic function body whose return ``T`` is the
1661
+ # local's annotation), the matcher's param-side TPRef branch would
1662
+ # unconditionally bind-and-accept, falsely matching every candidate.
1663
+ # The generic branch above is defended via ``_is_useful_seed_binding``'s
1664
+ # TPRef filter; mirror the defense here for the non-generic branch.
1665
+ lhs_unwrapped = unwrap_qualifiers(lhs_hint)
1666
+ if contains_type_param(lhs_unwrapped):
1667
+ return None
1668
+ if not self.match_type_with_inference(
1669
+ lhs_unwrapped,
1670
+ unwrap_qualifiers(func.return_type),
1671
+ {},
1672
+ ):
1673
+ return None
1674
+ out: list[TpyType | None] = []
1675
+ for i in range(n_args):
1676
+ # Mirrors ``seeded_arg_hint``: trailing args beyond fixed positional
1677
+ # slots target the variadic param's element type.
1678
+ if i < len(func.params):
1679
+ target = func.params[i]
1680
+ elif func.params and func.params[-1].is_variadic:
1681
+ target = func.params[-1]
1682
+ else:
1683
+ out.append(None)
1684
+ continue
1685
+ ptype = unwrap_ref_type(target.type)
1686
+ if target.is_variadic and is_span(ptype):
1687
+ ptype = unwrap_readonly(ptype.type_args[0])
1688
+ hint = unwrap_qualifiers(ptype)
1689
+ # Mirror ``post_substitute_hint``: an ``Optional[T]`` param accepts
1690
+ # a bare T arg, so the seeded hint exposes the inner T to the
1691
+ # inner ctor / call's seed instead of forcing it to match the
1692
+ # Optional shape.
1693
+ if isinstance(hint, OptionalType):
1694
+ hint = unwrap_qualifiers(hint.inner)
1695
+ out.append(hint)
1696
+ return out
1697
+
1698
+ def _inherits_protocol(self, concrete: TpyType, protocol: NominalType) -> bool:
1699
+ """Return True if `concrete` transitively implements a @dynamic protocol matching `protocol`.
1700
+
1701
+ Decides whether arg-derived T should be kept (Covariant[T] handles the
1702
+ uplift) or replaced by the LHS-hint protocol T (structural-conform case
1703
+ that needs explicit heap wrapping). Same-named non-@dynamic protocols
1704
+ don't match -- only @dynamic protocols are relevant to the LHS-hint path.
1705
+
1706
+ @native records are excluded: their C++ struct is hand-written and
1707
+ almost certainly does not inherit the codegen-emitted protocol base
1708
+ (see ``codegen_cpp.protocols.directly_implements_dynamic`` for the
1709
+ full reasoning). Routing through Adapter is the only safe lowering,
1710
+ so this helper must agree with codegen's branching to avoid sema
1711
+ keeping a native T that codegen would later refuse.
1712
+ """
1713
+ if not isinstance(concrete, NominalType) or not concrete.is_user_record:
1714
+ return False
1715
+ record_info = self.ctx.registry.get_record(concrete.name)
1716
+ if record_info is None or record_info.is_native:
1717
+ return False
1718
+ if not is_subtype(record_info, protocol.name):
1719
+ return False
1720
+ proto_info = protocol_info_of(protocol)
1721
+ return proto_info is not None and proto_info.is_dynamic
1722
+
1723
+ def match_generic_constructor(
1724
+ self, params: list, arg_types: list[TpyType]
1725
+ ) -> dict[str, TpyType] | None:
1726
+ """Try to match constructor params against arg types and infer type parameters.
1727
+
1728
+ Returns dict of inferred type params (e.g., {"T": Int32}) on success, None on failure.
1729
+ Supports protocol params like "NativeIterable[T]" which infer T from element type.
1730
+
1731
+ Unlike match_type_with_inference, Ptr[TypeParam] params accept Ptr[readonly[X]] args,
1732
+ inferring T = readonly[X]. This lets Span(readonly_ptr, n) produce Span[readonly[T]]
1733
+ without needing an explicit type annotation. Function inference keeps the strict check
1734
+ to preserve helpful errors (e.g. "unsafe_store() requires a mutable pointer").
1735
+ """
1736
+ inferred: dict[str, TpyType] = {}
1737
+ for param, arg_type in zip(params, arg_types):
1738
+ pt = param.type
1739
+ # For Ptr[TypeParam] constructor params, allow T = readonly[X] inference
1740
+ # from Ptr[readonly[X]] arguments. This is safe here (constructor context)
1741
+ # because the resulting type (e.g. Span[readonly[T]]) captures readonly-ness.
1742
+ if (isinstance(pt, PtrType)
1743
+ and isinstance(pt.pointee, TypeParamRef)
1744
+ and isinstance(arg_type, PtrType)
1745
+ and arg_type.is_readonly
1746
+ and not pt.is_readonly):
1747
+ name = pt.pointee.name
1748
+ pointee = arg_type.pointee # ReadonlyType(X)
1749
+ if name in inferred:
1750
+ if not self.types_match_for_inference(inferred[name], pointee):
1751
+ return None
1752
+ else:
1753
+ inferred[name] = pointee
1754
+ continue
1755
+ if not self.match_type_with_inference(pt, arg_type, inferred):
1756
+ return None
1757
+ return inferred
1758
+
1759
+ def pending_list_matches_array(self, actual: PendingListType, expected: NominalType) -> bool:
1760
+ """Check if a pending list literal can match an Array type (including nested arrays)."""
1761
+ if actual.size != expected.type_args[1]:
1762
+ return False
1763
+
1764
+ actual_elem = actual.element_type
1765
+ expected_elem = expected.type_args[0]
1766
+
1767
+ if isinstance(actual_elem, PendingListType) and is_array(expected_elem):
1768
+ return self.pending_list_matches_array(actual_elem, expected_elem)
1769
+
1770
+ if actual_elem == expected_elem:
1771
+ return True
1772
+
1773
+ if isinstance(actual_elem, IntLiteralType) and is_integer_type(expected_elem):
1774
+ info = self.ctx.list_literals.get(actual.literal_id)
1775
+ if info:
1776
+ info.coerced_element_type = expected_elem
1777
+ return True
1778
+
1779
+ return False
1780
+
1781
+ def substitute_method_type_params(
1782
+ self, method: FunctionInfo, type_subst: dict[str, TpyType]
1783
+ ) -> FunctionInfo:
1784
+ """Substitute type parameters in a method signature.
1785
+
1786
+ Method's own type params (e.g. U on def transform[U]) are preserved
1787
+ as identity mappings (U -> TypeParamRef(U)). This is needed because
1788
+ substitute_type_params raises SemanticError for unknown params, and
1789
+ method-level params aren't in the class substitution dict.
1790
+ """
1791
+ effective_subst = dict(type_subst)
1792
+ for tp in method.type_params:
1793
+ if tp not in effective_subst:
1794
+ effective_subst[tp] = TypeParamRef(tp)
1795
+ substituted_params = [
1796
+ dc_replace(p, type=self.substitute_type_params(p.type, effective_subst))
1797
+ for p in method.params
1798
+ ]
1799
+ substituted_return = self.substitute_type_params(method.return_type, effective_subst)
1800
+
1801
+ # Substitute type params in bounds
1802
+ substituted_bounds = {}
1803
+ if method.type_param_bounds:
1804
+ for param_name, bound in method.type_param_bounds.items():
1805
+ substituted_bounds[param_name] = self.substitute_type_params(bound, effective_subst)
1806
+
1807
+ return FunctionInfo(
1808
+ name=method.name,
1809
+ params=substituted_params,
1810
+ return_type=substituted_return,
1811
+ is_noalloc=method.is_noalloc,
1812
+ is_readonly=method.is_readonly,
1813
+ is_consuming=method.is_consuming,
1814
+ is_method=method.is_method,
1815
+ is_staticmethod=method.is_staticmethod,
1816
+ # is_async / is_generator are invariant under type-arg substitution.
1817
+ is_async=method.is_async,
1818
+ is_generator=method.is_generator,
1819
+ is_builtin_function=method.is_builtin_function,
1820
+ type_params=method.type_params,
1821
+ type_param_bounds=substituted_bounds if substituted_bounds else method.type_param_bounds,
1822
+ linkage=method.linkage,
1823
+ native_name=method.native_name,
1824
+ native_function=method.native_function,
1825
+ native_preserves_refs=method.native_preserves_refs,
1826
+ cpp_template=method.cpp_template,
1827
+ value_ptr_coercion=method.value_ptr_coercion,
1828
+ error_return_type=method.error_return_type,
1829
+ qualified_name=method.qualified_name,
1830
+ # Propagate so post-substitution ``min_args`` correctly excludes
1831
+ # the ``**kw`` slot from the positional count (the property
1832
+ # filters via ``not (self.kwarg_name and p.name == self.kwarg_name)``).
1833
+ # Without this, the arity gate at ``candidate_arg_hints`` and any
1834
+ # other downstream consumer reads an inflated min_args.
1835
+ kwarg_name=method.kwarg_name,
1836
+ # Preserve analysis-derived facts -- indices are positional (unaffected by
1837
+ # type substitution) and needed by call-site borrow/mutation checks.
1838
+ return_borrows_from=method.return_borrows_from,
1839
+ mutated_params=method.mutated_params,
1840
+ structural_mutated_params=method.structural_mutated_params,
1841
+ canonical_fi=method.root,
1842
+ )
1843
+
1844
+ def get_deref_target_type(self, typ: TpyType, is_readonly: bool = False) -> TpyType | None:
1845
+ """If typ has __deref__(), return resolved return type. Else None."""
1846
+ record_info = self.ctx.registry.get_record_for_type(typ)
1847
+ if not record_info:
1848
+ return None
1849
+ overloads = record_info.get_method_overloads("__deref__")
1850
+ if not overloads:
1851
+ return None
1852
+ method = resolve_overload(
1853
+ overloads, [], is_readonly_receiver=is_readonly)
1854
+ if method is None:
1855
+ return None
1856
+ type_subst = self.build_type_substitution(typ)
1857
+ if type_subst:
1858
+ method = self.substitute_method_type_params(method, type_subst)
1859
+ return unwrap_ref_type(method.return_type)
1860
+
1861
+ def get_deref_coercion_target(self, typ: TpyType) -> TpyType | None:
1862
+ """Get the deref target for coercion purposes.
1863
+
1864
+ Like get_deref_target_type() but excludes Ptr[readonly[T]] -- record params
1865
+ are T& (mutable ref) but deref_check(const T*) returns const T&.
1866
+
1867
+ Note: does not pass is_readonly because ReadonlyType is already stripped
1868
+ from the actual type before this is called (by check_type_compatible).
1869
+ For user types with @auto_readonly __deref__, this means the mutable
1870
+ overload is selected. This is correct for value types (copies) and
1871
+ returns, but could be wrong for non-value deref targets in assignment
1872
+ context. Low risk in practice since user deref types typically return
1873
+ value types.
1874
+ """
1875
+ if is_readonly_ptr(typ):
1876
+ return None
1877
+ return self.get_deref_target_type(typ)
1878
+
1879
+