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.
- tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
- tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
- tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
- tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
- tpyc/__init__.py +104 -0
- tpyc/__main__.py +6 -0
- tpyc/_buildinfo.py +1 -0
- tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
- tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
- tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
- tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
- tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
- tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
- tpyc/_data/lib/tpy/_functools_macros.py +80 -0
- tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
- tpyc/_data/lib/tpy/argparse.py +2062 -0
- tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
- tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
- tpyc/_data/lib/tpy/base64.py +410 -0
- tpyc/_data/lib/tpy/bisect.py +39 -0
- tpyc/_data/lib/tpy/builtins.py +38 -0
- tpyc/_data/lib/tpy/dataclasses.py +354 -0
- tpyc/_data/lib/tpy/enum.py +23 -0
- tpyc/_data/lib/tpy/functools.py +33 -0
- tpyc/_data/lib/tpy/hashlib.py +206 -0
- tpyc/_data/lib/tpy/heapq.py +118 -0
- tpyc/_data/lib/tpy/io.py +395 -0
- tpyc/_data/lib/tpy/json.py +221 -0
- tpyc/_data/lib/tpy/math.py +406 -0
- tpyc/_data/lib/tpy/random.py +597 -0
- tpyc/_data/lib/tpy/re.py +467 -0
- tpyc/_data/lib/tpy/socket.py +379 -0
- tpyc/_data/lib/tpy/struct.py +178 -0
- tpyc/_data/lib/tpy/sys.py +40 -0
- tpyc/_data/lib/tpy/time.py +39 -0
- tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
- tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
- tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
- tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
- tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
- tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
- tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
- tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
- tpyc/_data/lib/tpy/tpy/_version.py +29 -0
- tpyc/_data/lib/tpy/tpy/bits.py +28 -0
- tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
- tpyc/_data/lib/tpy/tpy/extern.py +8 -0
- tpyc/_data/lib/tpy/tpy/mem.py +49 -0
- tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
- tpyc/_data/lib/tpy/tpy/version.py +21 -0
- tpyc/_data/lib/tpy/typing.py +13 -0
- tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
- tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
- tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
- tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
- tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
- tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
- tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
- tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
- tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
- tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
- tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
- tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
- tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
- tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
- tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
- tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
- tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
- tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
- tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
- tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
- tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
- tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
- tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
- tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
- tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
- tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
- tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
- tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
- tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
- tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
- tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
- tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
- tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
- tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
- tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
- tpyc/_data/runtime/cpp/third_party/README.md +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
- tpyc/build/__init__.py +7 -0
- tpyc/build/pcre2.py +122 -0
- tpyc/build/third_party.py +413 -0
- tpyc/cli.py +822 -0
- tpyc/codegen_cpp/__init__.py +18 -0
- tpyc/codegen_cpp/builtins.py +484 -0
- tpyc/codegen_cpp/context.py +2064 -0
- tpyc/codegen_cpp/expressions.py +5940 -0
- tpyc/codegen_cpp/functions.py +1913 -0
- tpyc/codegen_cpp/gen_async.py +3258 -0
- tpyc/codegen_cpp/gen_generators.py +657 -0
- tpyc/codegen_cpp/generator.py +2258 -0
- tpyc/codegen_cpp/match.py +1997 -0
- tpyc/codegen_cpp/param_const.py +172 -0
- tpyc/codegen_cpp/protocols.py +907 -0
- tpyc/codegen_cpp/records.py +1654 -0
- tpyc/codegen_cpp/resumable_cfg.py +1651 -0
- tpyc/codegen_cpp/statements.py +4963 -0
- tpyc/codegen_cpp/string_dispatch.py +76 -0
- tpyc/codegen_cpp/test_context.py +46 -0
- tpyc/codegen_cpp/test_param_const.py +113 -0
- tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
- tpyc/codegen_cpp/type_resolution.py +53 -0
- tpyc/codegen_cpp/types.py +436 -0
- tpyc/codegen_cpp/variant_access.py +135 -0
- tpyc/coercions.py +749 -0
- tpyc/compilation_context.py +57 -0
- tpyc/compiler.py +3945 -0
- tpyc/cycle_detection.py +358 -0
- tpyc/diagnostics.py +135 -0
- tpyc/dump_types.py +353 -0
- tpyc/frontend_diagnostics.py +47 -0
- tpyc/frontend_ir/__init__.py +140 -0
- tpyc/frontend_ir/lower.py +1098 -0
- tpyc/frontend_ir/nodes.py +718 -0
- tpyc/frontend_ir/resolver_adapter.py +151 -0
- tpyc/frontend_plugin.py +209 -0
- tpyc/install_docs.py +81 -0
- tpyc/liveness.py +756 -0
- tpyc/macro_api.py +1724 -0
- tpyc/macro_loader.py +497 -0
- tpyc/module_names.py +64 -0
- tpyc/modules/__init__.py +31 -0
- tpyc/modules/defs.py +89 -0
- tpyc/modules/registry.py +36 -0
- tpyc/modules/resolver.py +192 -0
- tpyc/modules/type_resolution.py +629 -0
- tpyc/namespace.py +172 -0
- tpyc/parse/__init__.py +84 -0
- tpyc/parse/imports.py +490 -0
- tpyc/parse/nodes.py +1732 -0
- tpyc/parse/parser.py +4043 -0
- tpyc/parse/resolve_refs.py +466 -0
- tpyc/parse/type_resolver.py +1060 -0
- tpyc/prescan.py +254 -0
- tpyc/qnames.py +149 -0
- tpyc/repl.py +529 -0
- tpyc/repl_backends.py +848 -0
- tpyc/sema/__init__.py +21 -0
- tpyc/sema/analyzer.py +3625 -0
- tpyc/sema/bound_check.py +72 -0
- tpyc/sema/builder_trace.py +684 -0
- tpyc/sema/calls.py +5406 -0
- tpyc/sema/compatibility.py +2107 -0
- tpyc/sema/context.py +1243 -0
- tpyc/sema/expressions.py +3737 -0
- tpyc/sema/flow_facts.py +199 -0
- tpyc/sema/init_tracker.py +150 -0
- tpyc/sema/list_literals.py +69 -0
- tpyc/sema/literal_utils.py +27 -0
- tpyc/sema/local_deduction.py +1088 -0
- tpyc/sema/macros.py +179 -0
- tpyc/sema/match.py +1177 -0
- tpyc/sema/method_expansion.py +347 -0
- tpyc/sema/methods.py +2197 -0
- tpyc/sema/mutation_propagation.py +268 -0
- tpyc/sema/narrowing.py +857 -0
- tpyc/sema/numeric_lattice.py +160 -0
- tpyc/sema/operators.py +402 -0
- tpyc/sema/overloads.py +841 -0
- tpyc/sema/protocols.py +1209 -0
- tpyc/sema/reach_analysis.py +202 -0
- tpyc/sema/registration.py +3156 -0
- tpyc/sema/scope_tracker.py +193 -0
- tpyc/sema/statements.py +4426 -0
- tpyc/sema/type_ops.py +1879 -0
- tpyc/sema/value_range.py +181 -0
- tpyc/symbol_binding.py +259 -0
- tpyc/test_c3_mro.py +208 -0
- tpyc/test_cli_argv.py +52 -0
- tpyc/test_compiler.py +559 -0
- tpyc/test_contains_type_param.py +101 -0
- tpyc/test_cycle_detection.py +221 -0
- tpyc/test_dump_types.py +225 -0
- tpyc/test_install_docs.py +65 -0
- tpyc/test_local_cpp_form.py +135 -0
- tpyc/test_macro_loader.py +76 -0
- tpyc/test_method_expansion.py +254 -0
- tpyc/test_nominal_identity.py +182 -0
- tpyc/test_overloads.py +410 -0
- tpyc/test_parse.py +303 -0
- tpyc/test_parse_type_ref.py +506 -0
- tpyc/test_parse_version_info.py +58 -0
- tpyc/test_reach_analysis.py +72 -0
- tpyc/test_ref_type.py +216 -0
- tpyc/test_send_sync_substitution.py +276 -0
- tpyc/test_tuple_mutation_propagation.py +206 -0
- tpyc/test_type_def_registry.py +1729 -0
- tpyc/test_union_types.py +195 -0
- tpyc/type_def_registry.py +975 -0
- 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
|
+
|