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