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/context.py
ADDED
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Semantic Analysis Context
|
|
3
|
+
|
|
4
|
+
Contains the shared state that is passed to all semantic analysis components.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Iterator, Literal, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from ..macro_loader import MacroRegistry
|
|
15
|
+
from ..parse.nodes import SourceLocation
|
|
16
|
+
from .value_range import ValueRange
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..parse.type_resolver import TypeResolver
|
|
20
|
+
|
|
21
|
+
from ..typesys import (
|
|
22
|
+
TpyType, TypeRegistry, ListLiteralInfo, DictLiteralInfo, SetLiteralInfo, ViewVarInfo, TypeParamKind, IntLiteralType,
|
|
23
|
+
INT32, BIGINT, NominalType, ReadonlyType, OwnType, OptionalType, UnionType, TupleType,
|
|
24
|
+
RecursiveAliasInstanceType, recursive_union_alternatives,
|
|
25
|
+
PendingListType, PendingDictType, PendingSetType,
|
|
26
|
+
PendingGenericInstanceType, PendingGenericInstanceInfo,
|
|
27
|
+
ViewTypeFamily, PendingViewType, PendingStrType, VIEW_TYPE_FAMILIES,
|
|
28
|
+
unwrap_readonly, unwrap_ref_type, unwrap_qualifiers,
|
|
29
|
+
is_dyn_protocol,
|
|
30
|
+
)
|
|
31
|
+
from ..namespace import Namespace
|
|
32
|
+
from ..type_def_registry import int_traits_of
|
|
33
|
+
from ..parse import (
|
|
34
|
+
TpyExpr, TpyStmt, TpyRecord, TpyFunction, TpyVarDecl, TpyMethodCall,
|
|
35
|
+
TpyCall, TpyCoerce, TpyName, TpySubscript, TpyFieldAccess, TpyBinOp, TpyIfExpr,
|
|
36
|
+
TpyNestedDef,
|
|
37
|
+
)
|
|
38
|
+
from ..diagnostics import Diagnostic, DiagnosticLevel, SemanticError, Scope
|
|
39
|
+
|
|
40
|
+
# Tuple of all pending container types -- use in isinstance checks so adding
|
|
41
|
+
# a new container type requires updating only this one constant.
|
|
42
|
+
PENDING_CONTAINER_TYPES = (PendingListType, PendingDictType, PendingSetType)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def addr_taken_roots(expr: TpyExpr) -> list[str]:
|
|
46
|
+
"""Return all variable names whose storage is potentially aliased by expr.
|
|
47
|
+
|
|
48
|
+
Used to mark params as mutated when their address is taken (directly or
|
|
49
|
+
implicitly). Handles or/and (TpyBinOp ||/&&) and ternary (TpyIfExpr),
|
|
50
|
+
returning all possible roots across branches.
|
|
51
|
+
"""
|
|
52
|
+
if isinstance(expr, TpyCoerce):
|
|
53
|
+
return addr_taken_roots(expr.expr)
|
|
54
|
+
if isinstance(expr, TpyName):
|
|
55
|
+
return [expr.name]
|
|
56
|
+
if isinstance(expr, TpySubscript):
|
|
57
|
+
return addr_taken_roots(expr.obj)
|
|
58
|
+
if isinstance(expr, TpyFieldAccess):
|
|
59
|
+
return addr_taken_roots(expr.obj)
|
|
60
|
+
if isinstance(expr, TpyBinOp) and expr.op in ("||", "&&"):
|
|
61
|
+
return addr_taken_roots(expr.left) + addr_taken_roots(expr.right)
|
|
62
|
+
if isinstance(expr, TpyIfExpr):
|
|
63
|
+
return addr_taken_roots(expr.then_expr) + addr_taken_roots(expr.else_expr)
|
|
64
|
+
return []
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _storage_key(expr: TpyExpr) -> str | None:
|
|
68
|
+
"""Extract storage key: 'name' or 'name.field' for single-level field access.
|
|
69
|
+
|
|
70
|
+
Used by the borrow tracker as a dict key to identify storage that can be
|
|
71
|
+
borrowed from or mutated. Supports dotted paths so that ``self.items``
|
|
72
|
+
and ``obj.field`` are tracked separately from ``self`` / ``obj``.
|
|
73
|
+
"""
|
|
74
|
+
if isinstance(expr, TpyName):
|
|
75
|
+
return expr.name
|
|
76
|
+
if isinstance(expr, TpyFieldAccess) and isinstance(expr.obj, TpyName):
|
|
77
|
+
return f"{expr.obj.name}.{expr.field}"
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _storage_root(key: str) -> str:
|
|
82
|
+
"""Strip the field suffix off a dotted storage key ('self.items' -> 'self').
|
|
83
|
+
|
|
84
|
+
Inverse of `_storage_key`: callers that need to look up the variable name
|
|
85
|
+
a dotted key was built from (param-name table, loop-var dict) use this.
|
|
86
|
+
"""
|
|
87
|
+
dot = key.find(".")
|
|
88
|
+
return key if dot == -1 else key[:dot]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def expr_yields_non_null_ptr(expr: TpyExpr, non_null_vars: set[str]) -> bool:
|
|
92
|
+
"""Whether evaluating `expr` produces a Ptr value with known non-null provenance.
|
|
93
|
+
|
|
94
|
+
Three independent sources, all unified here so consumers (local-init,
|
|
95
|
+
rebind, future return-site / expression-context uses) don't re-enumerate:
|
|
96
|
+
|
|
97
|
+
1. Address-taking coercion (`&{e}` -- the `produces_non_null_ptr` flag on
|
|
98
|
+
the Coercion). Covers `p: Ptr[T] = x` and the inheritance / @dynamic
|
|
99
|
+
upcasts. A chain of coercions is walked: any non-address-taking outer
|
|
100
|
+
coercion (e.g. the trivial `Ptr[T] -> Ptr[readonly[T]]`) is transparent
|
|
101
|
+
and provenance is inherited from the inner expression.
|
|
102
|
+
2. Ptr-returning call. Either the call's return type is itself Ptr-typed
|
|
103
|
+
(`take_ptr(x)`, explicit `Ptr(x)` constructor) or the function carries
|
|
104
|
+
the `@value_ptr_coercion` flag (its Ptr param coerces a T arg via `&`).
|
|
105
|
+
3. Read from a name that earlier analysis already marked non-null.
|
|
106
|
+
"""
|
|
107
|
+
while isinstance(expr, TpyCoerce):
|
|
108
|
+
if expr.coercion.produces_non_null_ptr:
|
|
109
|
+
return True
|
|
110
|
+
expr = expr.expr
|
|
111
|
+
if isinstance(expr, TpyCall) and expr.args:
|
|
112
|
+
if expr.call_type is not None and expr.call_type.is_pointer():
|
|
113
|
+
return True
|
|
114
|
+
fi = expr.resolved_function_info
|
|
115
|
+
if fi is not None and fi.value_ptr_coercion:
|
|
116
|
+
return True
|
|
117
|
+
if isinstance(expr, TpyName):
|
|
118
|
+
return expr.name in non_null_vars
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _borrow_storage_root(expr: TpyExpr) -> str | None:
|
|
123
|
+
"""Extract the storage key whose storage is borrowed by this expression.
|
|
124
|
+
|
|
125
|
+
Handles simple names (``items``), subscript on names or single-level
|
|
126
|
+
field access (``items[i]``, ``self.items[i]``), and field access on
|
|
127
|
+
names (``obj.field``). Returns None for deeper nesting or rvalues
|
|
128
|
+
(conservative -- no borrow registered).
|
|
129
|
+
"""
|
|
130
|
+
if isinstance(expr, TpyCoerce):
|
|
131
|
+
return _borrow_storage_root(expr.expr)
|
|
132
|
+
if isinstance(expr, TpyName):
|
|
133
|
+
return expr.name
|
|
134
|
+
if isinstance(expr, TpySubscript):
|
|
135
|
+
return _storage_key(expr.obj)
|
|
136
|
+
if isinstance(expr, TpyFieldAccess):
|
|
137
|
+
return _storage_key(expr)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class BorrowKind(Enum):
|
|
142
|
+
"""Kind of borrow relationship between a borrower and its storage."""
|
|
143
|
+
ALIAS = "alias" # whole-container alias (safe through mutations)
|
|
144
|
+
FIELD = "field" # field-level reference
|
|
145
|
+
ITER = "iter" # for-loop iterator
|
|
146
|
+
ELEMENT = "element" # subscript element reference
|
|
147
|
+
PTR = "ptr" # pointer into storage
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# Restrictiveness order: PTR/ITER/ELEMENT > FIELD > ALIAS. Used by retarget
|
|
151
|
+
# logic to promote a chained borrow to the most-restrictive kind in the chain
|
|
152
|
+
# (an ALIAS of an ELEMENT borrower is invalidated by structural mutation of
|
|
153
|
+
# the source container) and by FlowFacts borrow merging.
|
|
154
|
+
BORROW_KIND_RANK: dict[BorrowKind, int] = {
|
|
155
|
+
BorrowKind.ALIAS: 0,
|
|
156
|
+
BorrowKind.FIELD: 1,
|
|
157
|
+
BorrowKind.ITER: 2,
|
|
158
|
+
BorrowKind.ELEMENT: 3,
|
|
159
|
+
BorrowKind.PTR: 3,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class BorrowTracker:
|
|
164
|
+
"""Tracks borrow relationships between variables for mutation safety.
|
|
165
|
+
|
|
166
|
+
Manages which variables borrow storage from other variables, enabling
|
|
167
|
+
conflict detection when borrowed storage is mutated.
|
|
168
|
+
|
|
169
|
+
Note: string view source tracking (str_source_borrows) and loop var
|
|
170
|
+
mutation tracking (mutated_loop_vars) remain on SemanticContext since
|
|
171
|
+
they reference string deduction and loop optimization state respectively.
|
|
172
|
+
TODO: consider unifying all borrow-like tracking here if a broader
|
|
173
|
+
"deferred type tracking" system emerges.
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
__slots__ = ('borrows', 'borrow_kinds')
|
|
177
|
+
|
|
178
|
+
def __init__(self) -> None:
|
|
179
|
+
self.borrows: dict[str, set[str]] = {}
|
|
180
|
+
self.borrow_kinds: dict[tuple[str, str], BorrowKind] = {}
|
|
181
|
+
|
|
182
|
+
def reset(self) -> None:
|
|
183
|
+
"""Clear all borrow state (called between function analyses)."""
|
|
184
|
+
self.borrows.clear()
|
|
185
|
+
self.borrow_kinds.clear()
|
|
186
|
+
|
|
187
|
+
def add_borrow(self, storage: str, borrower: str, kind: BorrowKind = BorrowKind.ALIAS) -> None:
|
|
188
|
+
"""Record that ``borrower`` borrows from ``storage``."""
|
|
189
|
+
self.borrows.setdefault(storage, set()).add(borrower)
|
|
190
|
+
self.borrow_kinds[(storage, borrower)] = kind
|
|
191
|
+
|
|
192
|
+
def remove_borrower(self, borrower: str) -> None:
|
|
193
|
+
"""Remove all borrows held by ``borrower`` (e.g. on reassignment)."""
|
|
194
|
+
to_clean: list[str] = []
|
|
195
|
+
for storage, borrowers in self.borrows.items():
|
|
196
|
+
if borrower in borrowers:
|
|
197
|
+
borrowers.discard(borrower)
|
|
198
|
+
self.borrow_kinds.pop((storage, borrower), None)
|
|
199
|
+
if not borrowers:
|
|
200
|
+
to_clean.append(storage)
|
|
201
|
+
for storage in to_clean:
|
|
202
|
+
del self.borrows[storage]
|
|
203
|
+
|
|
204
|
+
def retarget_storage_borrows(self, storage: str) -> None:
|
|
205
|
+
"""Reassignment of ``storage``: retarget its borrowers to the upstream source.
|
|
206
|
+
|
|
207
|
+
The reassigned variable is generated as a ``T*`` pointer-local in C++,
|
|
208
|
+
so any borrower like ``view = storage`` aliases whatever ``storage``
|
|
209
|
+
currently points into. After the reassignment we want chains to keep
|
|
210
|
+
tracking the original container, not break silently.
|
|
211
|
+
|
|
212
|
+
The retargeted borrow kind is the most-restrictive in the chain
|
|
213
|
+
(PTR/ITER/ELEMENT > FIELD > ALIAS): an ALIAS of an ELEMENT borrower
|
|
214
|
+
is still invalidated by structural mutation of the source container.
|
|
215
|
+
|
|
216
|
+
Field-path borrows (``storage.*``) refer to the old object's fields
|
|
217
|
+
and are dropped unconditionally -- the variable will be rebound to a
|
|
218
|
+
different object, so those paths are unreachable.
|
|
219
|
+
|
|
220
|
+
If ``storage`` itself was not borrowing anything (no upstream chain),
|
|
221
|
+
its borrowers had nowhere to retarget to and are dropped.
|
|
222
|
+
"""
|
|
223
|
+
upstream = self.borrow_source(storage)
|
|
224
|
+
upstream_kind = self.borrow_kinds.get((upstream, storage)) if upstream else None
|
|
225
|
+
|
|
226
|
+
borrowers = self.borrows.pop(storage, None)
|
|
227
|
+
if borrowers:
|
|
228
|
+
if upstream is not None and upstream_kind is not None:
|
|
229
|
+
for b in borrowers:
|
|
230
|
+
child_kind = self.borrow_kinds.pop((storage, b), None)
|
|
231
|
+
if child_kind is None:
|
|
232
|
+
continue
|
|
233
|
+
promoted = (child_kind
|
|
234
|
+
if BORROW_KIND_RANK[child_kind] >= BORROW_KIND_RANK[upstream_kind]
|
|
235
|
+
else upstream_kind)
|
|
236
|
+
existing = self.borrow_kinds.get((upstream, b))
|
|
237
|
+
if existing is None or BORROW_KIND_RANK[promoted] > BORROW_KIND_RANK[existing]:
|
|
238
|
+
self.borrows.setdefault(upstream, set()).add(b)
|
|
239
|
+
self.borrow_kinds[(upstream, b)] = promoted
|
|
240
|
+
else:
|
|
241
|
+
for b in borrowers:
|
|
242
|
+
self.borrow_kinds.pop((storage, b), None)
|
|
243
|
+
|
|
244
|
+
# Field-path borrows (storage.X) are dropped: the variable rebinds to
|
|
245
|
+
# a different object, so dotted-key borrows are unreachable.
|
|
246
|
+
prefix = storage + "."
|
|
247
|
+
to_remove = [k for k in self.borrows if k.startswith(prefix)]
|
|
248
|
+
for k in to_remove:
|
|
249
|
+
for b in self.borrows.pop(k):
|
|
250
|
+
self.borrow_kinds.pop((k, b), None)
|
|
251
|
+
|
|
252
|
+
def has_iter_borrow(self, storage_name: str) -> bool:
|
|
253
|
+
"""Check if a variable is borrowed by an active for-loop iterator."""
|
|
254
|
+
borrowers = self.borrows.get(storage_name)
|
|
255
|
+
return borrowers is not None and "__for_iter" in borrowers
|
|
256
|
+
|
|
257
|
+
def has_element_borrow(self, storage_name: str) -> bool:
|
|
258
|
+
"""Check if a variable has element-level or iterator borrows.
|
|
259
|
+
|
|
260
|
+
Returns True for borrows that can be invalidated by structural
|
|
261
|
+
mutations (reallocation, insertion, deletion). Returns False for
|
|
262
|
+
whole-container alias borrows which are safe through mutations.
|
|
263
|
+
"""
|
|
264
|
+
borrowers = self.borrows.get(storage_name)
|
|
265
|
+
if not borrowers:
|
|
266
|
+
return False
|
|
267
|
+
_INVALIDATING = (BorrowKind.ITER, BorrowKind.ELEMENT, BorrowKind.PTR)
|
|
268
|
+
return any(
|
|
269
|
+
self.borrow_kinds.get((storage_name, b)) in _INVALIDATING
|
|
270
|
+
for b in borrowers
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def has_borrow_of_kinds(self, storage: str, kinds: tuple[BorrowKind, ...]) -> bool:
|
|
274
|
+
"""Check if any borrower of storage has one of the given borrow kinds."""
|
|
275
|
+
borrowers = self.borrows.get(storage)
|
|
276
|
+
if not borrowers:
|
|
277
|
+
return False
|
|
278
|
+
return any(
|
|
279
|
+
self.borrow_kinds.get((storage, b)) in kinds
|
|
280
|
+
for b in borrowers
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
def effective_storage(self, name: str) -> str:
|
|
284
|
+
"""Resolve alias chains to find the underlying storage.
|
|
285
|
+
|
|
286
|
+
If ``name`` is an alias of another variable, follows the chain
|
|
287
|
+
(e.g. b -> a -> items) and returns the root storage. Returns
|
|
288
|
+
``name`` itself when it is not an alias borrower.
|
|
289
|
+
"""
|
|
290
|
+
visited: set[str] = {name}
|
|
291
|
+
current = name
|
|
292
|
+
while True:
|
|
293
|
+
found = None
|
|
294
|
+
for storage, borrowers in self.borrows.items():
|
|
295
|
+
if current in borrowers and self.borrow_kinds.get((storage, current)) is BorrowKind.ALIAS:
|
|
296
|
+
found = storage
|
|
297
|
+
break
|
|
298
|
+
if found is None or found in visited:
|
|
299
|
+
return current
|
|
300
|
+
visited.add(found)
|
|
301
|
+
current = found
|
|
302
|
+
|
|
303
|
+
def borrow_kind_of(self, name: str) -> 'BorrowKind | None':
|
|
304
|
+
"""Return the kind of borrow that 'name' holds, or None if not a borrower."""
|
|
305
|
+
for storage, borrowers in self.borrows.items():
|
|
306
|
+
if name in borrowers:
|
|
307
|
+
return self.borrow_kinds.get((storage, name))
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
def is_deferred_borrow(self, name: str) -> bool:
|
|
311
|
+
"""Return True if name ultimately traces back to a deferred ELEMENT borrow.
|
|
312
|
+
|
|
313
|
+
A variable is deferred if it is an ELEMENT borrow, or an ALIAS/FIELD borrow
|
|
314
|
+
of a deferred variable (transitively). PTR/ITER borrows are not deferred.
|
|
315
|
+
Termination is guaranteed because borrow chains are acyclic.
|
|
316
|
+
"""
|
|
317
|
+
kind = self.borrow_kind_of(name)
|
|
318
|
+
if kind == BorrowKind.ELEMENT:
|
|
319
|
+
return True
|
|
320
|
+
if kind in (BorrowKind.ALIAS, BorrowKind.FIELD):
|
|
321
|
+
source = self.borrow_source(name)
|
|
322
|
+
if source is not None:
|
|
323
|
+
return self.is_deferred_borrow(source)
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
def borrow_source(self, name: str) -> str | None:
|
|
327
|
+
"""Return the direct borrow source of name (any borrow kind), or None.
|
|
328
|
+
|
|
329
|
+
Unlike effective_storage (ALIAS-only), this finds the container for
|
|
330
|
+
ITER/ELEMENT/FIELD/PTR borrows too -- used to trace loop-var addresses
|
|
331
|
+
back to the source container.
|
|
332
|
+
"""
|
|
333
|
+
for storage, borrowers in self.borrows.items():
|
|
334
|
+
if name in borrowers:
|
|
335
|
+
return storage
|
|
336
|
+
return None
|
|
337
|
+
|
|
338
|
+
def effective_storage_through_borrows(self, name: str) -> str:
|
|
339
|
+
"""Follow ALL borrow chains (ALIAS + ELEMENT + FIELD + PTR) to ultimate storage.
|
|
340
|
+
|
|
341
|
+
Unlike effective_storage (ALIAS-only), this traverses the full chain
|
|
342
|
+
so a write through an element ref can be traced back to its source param.
|
|
343
|
+
For example: w ALIAS-borrows v, v ELEMENT-borrows items -> returns items.
|
|
344
|
+
Cycle-safe via visited set.
|
|
345
|
+
"""
|
|
346
|
+
visited: set[str] = {name}
|
|
347
|
+
current = name
|
|
348
|
+
while True:
|
|
349
|
+
found = None
|
|
350
|
+
for storage, borrowers in self.borrows.items():
|
|
351
|
+
if current in borrowers:
|
|
352
|
+
found = storage
|
|
353
|
+
break
|
|
354
|
+
if found is None or found in visited:
|
|
355
|
+
return current
|
|
356
|
+
visited.add(found)
|
|
357
|
+
current = found
|
|
358
|
+
|
|
359
|
+
def freeze(self) -> frozenset[tuple[str, str, BorrowKind]]:
|
|
360
|
+
"""Snapshot borrow state as immutable triples for flow analysis."""
|
|
361
|
+
return frozenset(
|
|
362
|
+
(storage, borrower, self.borrow_kinds[(storage, borrower)])
|
|
363
|
+
for storage, borrowers in self.borrows.items()
|
|
364
|
+
for borrower in borrowers
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def restore_from_frozen(self, triples: frozenset[tuple[str, str, BorrowKind]]) -> None:
|
|
368
|
+
"""Restore borrow state from frozen triples."""
|
|
369
|
+
self.borrows.clear()
|
|
370
|
+
self.borrow_kinds.clear()
|
|
371
|
+
for storage, borrower, kind in triples:
|
|
372
|
+
self.borrows.setdefault(storage, set()).add(borrower)
|
|
373
|
+
self.borrow_kinds[(storage, borrower)] = kind
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class _ModuleInitSentinel:
|
|
377
|
+
"""Sentinel for module-level init context (not a real function, but not None either).
|
|
378
|
+
|
|
379
|
+
Truthy so that `if ctx.func.current_function:` passes, but fails
|
|
380
|
+
`isinstance(ctx.func.current_function, TpyFunction)` checks.
|
|
381
|
+
"""
|
|
382
|
+
__slots__ = ()
|
|
383
|
+
def __bool__(self) -> bool:
|
|
384
|
+
return True
|
|
385
|
+
def __repr__(self) -> str:
|
|
386
|
+
return "<MODULE_INIT>"
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
MODULE_INIT_CONTEXT = _ModuleInitSentinel()
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def is_body_like_scope(current_function: 'TpyFunction | _ModuleInitSentinel | None') -> bool:
|
|
393
|
+
"""True when the current context supports pending-type resolution.
|
|
394
|
+
|
|
395
|
+
Real function bodies and ``MODULE_INIT_CONTEXT`` both have a
|
|
396
|
+
``FunctionTrackingState`` with ``pending_resolutions`` plumbed through
|
|
397
|
+
and ``deduction.resolve_all()`` runs at the end of both (see
|
|
398
|
+
``analyzer._analyze_top_level``). Class bodies, registration-time
|
|
399
|
+
contexts, and no-context states do not, so empty literals and similar
|
|
400
|
+
late-typed constructs must reject there.
|
|
401
|
+
"""
|
|
402
|
+
return isinstance(current_function, TpyFunction) or current_function is MODULE_INIT_CONTEXT
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
@dataclass
|
|
406
|
+
class RecordContext:
|
|
407
|
+
"""State for the record currently being analyzed (type params, bounds)."""
|
|
408
|
+
record: TpyRecord | None = None
|
|
409
|
+
type_params: list[str] | None = None
|
|
410
|
+
type_param_kinds: list[TypeParamKind] | None = None
|
|
411
|
+
type_param_bounds: dict[str, TpyType] | None = None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
@dataclass
|
|
415
|
+
class FunctionTrackingState:
|
|
416
|
+
"""Per-function analysis state.
|
|
417
|
+
|
|
418
|
+
Extracted from SemanticContext to keep that class from growing unbounded.
|
|
419
|
+
Reset between functions and saved/restored for nested-def isolation.
|
|
420
|
+
Accessed explicitly by callers via ``ctx.func.<field>``.
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
# --- Analysis state (per-function) ---
|
|
424
|
+
current_scope: Scope | None = None
|
|
425
|
+
current_function: TpyFunction | _ModuleInitSentinel | None = None
|
|
426
|
+
current_ns: Namespace | None = None
|
|
427
|
+
loop_depth: int = 0
|
|
428
|
+
|
|
429
|
+
# --- List/dict/set literal tracking ---
|
|
430
|
+
variable_to_literal: dict[str, int] = field(default_factory=dict)
|
|
431
|
+
pending_resolutions: list[int] = field(default_factory=list)
|
|
432
|
+
pre_analyzed_method_args: dict[int, list[TpyType]] = field(default_factory=dict)
|
|
433
|
+
variable_to_dict_literal: dict[str, int] = field(default_factory=dict)
|
|
434
|
+
pending_dict_resolutions: list[int] = field(default_factory=list)
|
|
435
|
+
variable_to_set_literal: dict[str, int] = field(default_factory=dict)
|
|
436
|
+
pending_set_resolutions: list[int] = field(default_factory=list)
|
|
437
|
+
|
|
438
|
+
# --- Pending generic instance tracking ---
|
|
439
|
+
pending_generic_instances: dict[int, PendingGenericInstanceInfo] = field(default_factory=dict)
|
|
440
|
+
variable_to_generic_instance: dict[str, int] = field(default_factory=dict)
|
|
441
|
+
|
|
442
|
+
# --- String local tracking ---
|
|
443
|
+
variable_to_str_var: dict[str, int] = field(default_factory=dict)
|
|
444
|
+
str_source_borrows: dict[str, set[int]] = field(default_factory=dict)
|
|
445
|
+
pending_str_resolutions: list[int] = field(default_factory=list)
|
|
446
|
+
|
|
447
|
+
# --- Bytes local tracking ---
|
|
448
|
+
variable_to_bytes_var: dict[str, int] = field(default_factory=dict)
|
|
449
|
+
bytes_source_borrows: dict[str, set[int]] = field(default_factory=dict)
|
|
450
|
+
pending_bytes_resolutions: list[int] = field(default_factory=list)
|
|
451
|
+
|
|
452
|
+
# --- Pinned-view alias tracking (StrView/BytesView annotations) ---
|
|
453
|
+
# source_name -> set of pinned-view borrower names. Pending views fall
|
|
454
|
+
# back to the owned type via source_mutated; pinned views can't, so we
|
|
455
|
+
# warn at source reassignment that the view dangles.
|
|
456
|
+
pinned_view_aliases: dict[str, set[str]] = field(default_factory=dict)
|
|
457
|
+
|
|
458
|
+
# --- Control flow ---
|
|
459
|
+
super_init_call: TpyMethodCall | None = None
|
|
460
|
+
super_del_call: TpyMethodCall | None = None
|
|
461
|
+
pending_loop_vars: dict[str, tuple[TpyType, TpyStmt, TpyStmt | None]] = field(default_factory=dict)
|
|
462
|
+
loop_vars: set[str] = field(default_factory=set)
|
|
463
|
+
mutated_loop_vars: set[str] = field(default_factory=set)
|
|
464
|
+
consumed_loop_vars: set[str] = field(default_factory=set)
|
|
465
|
+
deferred_loop_copy_warnings: dict[str, list[int]] = field(default_factory=dict)
|
|
466
|
+
loop_var_iterable: dict[str, str] = field(default_factory=dict)
|
|
467
|
+
|
|
468
|
+
# --- Scope escape tracking ---
|
|
469
|
+
var_scope_depth: dict[str, int] = field(default_factory=dict)
|
|
470
|
+
hoisted_vars: set[str] = field(default_factory=set)
|
|
471
|
+
rvalue_vars: set[str] = field(default_factory=set)
|
|
472
|
+
owned_locals: set[str] = field(default_factory=set)
|
|
473
|
+
# Accumulator: all locals that were ever owned. Survives FlowFacts
|
|
474
|
+
# save/restore (not in FlowFacts). Used to compute the exported
|
|
475
|
+
# movable_locals set at function end.
|
|
476
|
+
ever_owned_locals: set[str] = field(default_factory=set)
|
|
477
|
+
move_through_vars: set[str] = field(default_factory=set)
|
|
478
|
+
|
|
479
|
+
# --- Prescan / last-use ---
|
|
480
|
+
current_reassigned_vars: set[str] = field(default_factory=set)
|
|
481
|
+
current_lvalue_reassigned: set[str] = field(default_factory=set)
|
|
482
|
+
current_aug_assigned_vars: set[str] = field(default_factory=set)
|
|
483
|
+
|
|
484
|
+
# --- Definite-assignment tracking ---
|
|
485
|
+
definitely_assigned: set[str] = field(default_factory=set)
|
|
486
|
+
init_terminated: bool = False
|
|
487
|
+
narrowed_types: dict[str, TpyType] = field(default_factory=dict)
|
|
488
|
+
|
|
489
|
+
# --- Reassignment inference tracking ---
|
|
490
|
+
literal_default_vars: set[str] = field(default_factory=set)
|
|
491
|
+
literal_values: dict[str, list[int]] = field(default_factory=dict)
|
|
492
|
+
unresolved_none_vars: set[str] = field(default_factory=set)
|
|
493
|
+
write_history: dict[str, list[tuple[TpyType, TpyExpr]]] = field(default_factory=dict)
|
|
494
|
+
authoritative_types: dict[str, TpyType] = field(default_factory=dict)
|
|
495
|
+
authoritative_type_lines: dict[str, int] = field(default_factory=dict)
|
|
496
|
+
# Locals whose type was retroactively promoted from the literal-seeded
|
|
497
|
+
# default to a fixed-int target by a typed-slot use (ARG, RETURN, INIT,
|
|
498
|
+
# ASSIGN, SETITEM, FIELD, dict-key). Maps name to the loc of the use
|
|
499
|
+
# that triggered the promotion -- surfaced in later type-mismatch
|
|
500
|
+
# errors when a subsequent use disagrees with the locked type, and
|
|
501
|
+
# consulted by _analyze_assign to refresh stale existing_type when
|
|
502
|
+
# the RHS triggered the promotion of the LHS.
|
|
503
|
+
retro_widened_locs: dict[str, 'SourceLocation | None'] = field(default_factory=dict)
|
|
504
|
+
|
|
505
|
+
# --- Global declaration tracking ---
|
|
506
|
+
global_declarations: set[str] = field(default_factory=set)
|
|
507
|
+
|
|
508
|
+
# --- Nested def tracking ---
|
|
509
|
+
in_nested_def: bool = False
|
|
510
|
+
nested_def_name: str | None = None
|
|
511
|
+
outer_scope_locals: set[str] = field(default_factory=set)
|
|
512
|
+
current_nonlocal_names: set[str] = field(default_factory=set)
|
|
513
|
+
nested_def_names: set[str] = field(default_factory=set)
|
|
514
|
+
nested_def_escapes: set[str] = field(default_factory=set)
|
|
515
|
+
nested_def_nodes: dict[str, 'TpyNestedDef'] = field(default_factory=dict)
|
|
516
|
+
|
|
517
|
+
# --- Pointer provenance tracking ---
|
|
518
|
+
# Invariant: param_provenance_vars is a subset of safe_to_return_vars.
|
|
519
|
+
# Both sets merge with INTERSECT at branch/loop joins. The subset
|
|
520
|
+
# invariant is what lets a local stay safe-to-return when different
|
|
521
|
+
# branches reach the join via different safe sources (e.g. one branch
|
|
522
|
+
# param-derived, another a trusted call return) -- the OR-of-sources
|
|
523
|
+
# is materialized in safe_to_return_vars at write time, so intersection
|
|
524
|
+
# preserves it even though intersecting param_provenance_vars alone
|
|
525
|
+
# would lose it.
|
|
526
|
+
param_provenance_vars: set[str] = field(default_factory=set)
|
|
527
|
+
safe_to_return_vars: set[str] = field(default_factory=set)
|
|
528
|
+
non_null_ptr_vars: set[str] = field(default_factory=set)
|
|
529
|
+
# Narrowing accumulated during a single statement's expression analysis;
|
|
530
|
+
# flushed into non_null_ptr_vars at the statement boundary. Deferred so
|
|
531
|
+
# sibling accesses within an unspecified-order expression (e.g. `p.x + p.y`,
|
|
532
|
+
# `p.x = p.x + 1`) keep their individual checks -- C++ leaves arithmetic
|
|
533
|
+
# and RHS-vs-LHS sub-expression order unspecified (C++17 only sequences
|
|
534
|
+
# RHS-before-LHS for the whole assignment, not for sub-expressions),
|
|
535
|
+
# so within-statement elision would be unsafe.
|
|
536
|
+
pending_non_null_ptr_vars: set[str] = field(default_factory=set)
|
|
537
|
+
|
|
538
|
+
# --- Consumed variable tracking ---
|
|
539
|
+
consumed_vars: set[str] = field(default_factory=set)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
# --- Variable declaration tracking (per-function) ---
|
|
543
|
+
var_decl_by_name: dict[str, 'TpyVarDecl'] = field(default_factory=dict)
|
|
544
|
+
|
|
545
|
+
# --- Integer value range tracking ---
|
|
546
|
+
value_ranges: dict[str, 'ValueRange'] = field(default_factory=dict)
|
|
547
|
+
|
|
548
|
+
# --- Borrow tracking ---
|
|
549
|
+
borrow_tracker: BorrowTracker = field(default_factory=BorrowTracker)
|
|
550
|
+
|
|
551
|
+
# --- Parameter mutation inference ---
|
|
552
|
+
current_param_names: set[str] = field(default_factory=set)
|
|
553
|
+
current_param_name_to_idx: dict[str, int] = field(default_factory=dict)
|
|
554
|
+
current_mutated_param_names: set[str] = field(default_factory=set)
|
|
555
|
+
# Method type params whose `U: T` bound was used representationally in the
|
|
556
|
+
# body (e.g. `Ptr[U] -> Ptr[T]` coercion). Drained to FunctionInfo at body
|
|
557
|
+
# end; codegen reads it at call sites to decide adapter-wrap for structural
|
|
558
|
+
# conformers.
|
|
559
|
+
current_representational_params: set[str] = field(default_factory=set)
|
|
560
|
+
current_rebound_params: set[str] = field(default_factory=set)
|
|
561
|
+
current_call_edges: list = field(default_factory=list)
|
|
562
|
+
current_self_mutated: bool = False
|
|
563
|
+
current_self_struct_mutated: bool = False
|
|
564
|
+
current_struct_mutated_param_names: set[str] = field(default_factory=set)
|
|
565
|
+
current_returned_param_names: set[str] = field(default_factory=set)
|
|
566
|
+
current_consumed_own_params: set[str] = field(default_factory=set)
|
|
567
|
+
# Params whose address has been observed escaping into a mutable Ptr[T]
|
|
568
|
+
# field via `FIELD = PARAM`. Finalized to FunctionInfo.addr_escapes_params
|
|
569
|
+
# at body-analysis end; consumed by param-signature codegen to suppress
|
|
570
|
+
# the `const T&` default so the `¶m -> T*` store type-checks.
|
|
571
|
+
current_addr_escape_param_names: set[str] = field(default_factory=set)
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
@dataclass
|
|
575
|
+
class SemanticContext:
|
|
576
|
+
"""Shared state for all semantic analysis components.
|
|
577
|
+
|
|
578
|
+
Per-function tracking state is held in ``func`` (FunctionTrackingState).
|
|
579
|
+
Callers access it explicitly as ``ctx.func.<field>`` so the split between
|
|
580
|
+
module-wide state and per-function state is visible at every use site.
|
|
581
|
+
"""
|
|
582
|
+
|
|
583
|
+
# --- Core ---
|
|
584
|
+
registry: TypeRegistry
|
|
585
|
+
global_scope: Scope
|
|
586
|
+
default_int_type: TpyType = field(default_factory=lambda: INT32)
|
|
587
|
+
macro_registry: MacroRegistry | None = None
|
|
588
|
+
|
|
589
|
+
# --- Per-function state ---
|
|
590
|
+
# Accessed explicitly by callers as ``ctx.func.<field>``.
|
|
591
|
+
func: FunctionTrackingState = field(default_factory=FunctionTrackingState)
|
|
592
|
+
|
|
593
|
+
# --- Record context ---
|
|
594
|
+
record_ctx: RecordContext = field(default_factory=RecordContext)
|
|
595
|
+
|
|
596
|
+
# --- Type cache ---
|
|
597
|
+
expr_types: dict[int, TpyType] = field(default_factory=dict)
|
|
598
|
+
var_types: dict[int, TpyType] = field(default_factory=dict)
|
|
599
|
+
|
|
600
|
+
# --- Literal tracking (counters + registries persist across functions) ---
|
|
601
|
+
literal_counter: int = 0
|
|
602
|
+
list_literals: dict[int, ListLiteralInfo] = field(default_factory=dict)
|
|
603
|
+
dict_literals: dict[int, DictLiteralInfo] = field(default_factory=dict)
|
|
604
|
+
set_literals: dict[int, SetLiteralInfo] = field(default_factory=dict)
|
|
605
|
+
pending_generic_counter: int = 0
|
|
606
|
+
str_var_counter: int = 0
|
|
607
|
+
str_vars: dict[int, ViewVarInfo] = field(default_factory=dict)
|
|
608
|
+
bytes_var_counter: int = 0
|
|
609
|
+
bytes_vars: dict[int, ViewVarInfo] = field(default_factory=dict)
|
|
610
|
+
|
|
611
|
+
# --- Test annotation facts (persist across functions) ---
|
|
612
|
+
declared_var_types: dict[tuple[int, str], TpyType] = field(default_factory=dict)
|
|
613
|
+
ptr_deref_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
|
|
614
|
+
subscript_bounds_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
|
|
615
|
+
div_zero_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
|
|
616
|
+
cast_safe_facts: dict[tuple[int, str], bool] = field(default_factory=dict)
|
|
617
|
+
|
|
618
|
+
# --- Import tracking ---
|
|
619
|
+
imports: dict[str, set[tuple[str, str]] | None | str] = field(default_factory=dict)
|
|
620
|
+
bare_module_imports: set[str] = field(default_factory=set)
|
|
621
|
+
imported_names: dict[str, tuple[str, str]] = field(default_factory=dict)
|
|
622
|
+
|
|
623
|
+
# --- Cross-module support ---
|
|
624
|
+
module_name: str = "__main__"
|
|
625
|
+
# File-derived module name used by codegen for namespaces. Identical to
|
|
626
|
+
# `module_name` for non-entry modules; for the entry point, codegen uses
|
|
627
|
+
# the file name while `module_name` stays "__main__" for runtime
|
|
628
|
+
# `__name__` semantics. Set alongside `module_name` in `bind_imports`;
|
|
629
|
+
# consumed by `_register_union_wrappers` so the wrapper-index origin
|
|
630
|
+
# matches the comparison codegen does against its own `module_name`.
|
|
631
|
+
cpp_module_name: str = "__main__"
|
|
632
|
+
module_cpp_namespace: str | None = None # from # tpy: cpp_namespace directive
|
|
633
|
+
# Reference to the current module's `CompiledModule.exports`. Set by
|
|
634
|
+
# `Compiler._finalize_declarations` so the registration paths can
|
|
635
|
+
# find pre-populated skeleton RecordInfo / FunctionInfo /
|
|
636
|
+
# ProtocolInfo / enum NominalType objects (created by
|
|
637
|
+
# `_pre_populate_decl_exports`) and mutate them in place rather than
|
|
638
|
+
# allocating new ones. Peer modules' analyzer registries, populated
|
|
639
|
+
# by `bind_imports` via `module_info.{records,functions,...}`,
|
|
640
|
+
# capture references to the same skeletons; in-place mutation lets
|
|
641
|
+
# those peer registries see the freshly-finalized data without a
|
|
642
|
+
# post-hoc resync. None when no pre-populated exports are
|
|
643
|
+
# attached (e.g. ad-hoc analyzer construction in tests / REPL).
|
|
644
|
+
module_decl_exports: 'object | None' = None
|
|
645
|
+
# Reference to the current module's per-module attribute table
|
|
646
|
+
# (aliased from `CompiledModule.module_attributes`). Registration
|
|
647
|
+
# paths install bindings here as they mint/adopt records,
|
|
648
|
+
# functions, protocols, enums, variables, type aliases, and
|
|
649
|
+
# imports. None when no compiler-provided table is attached
|
|
650
|
+
# (ad-hoc analyzer construction in tests / REPL), in which case
|
|
651
|
+
# `install_binding` no-ops.
|
|
652
|
+
module_attributes: 'dict | None' = None
|
|
653
|
+
top_level_decls: dict[str, int] = field(default_factory=dict)
|
|
654
|
+
# Defining modules this module's code references. Populated by
|
|
655
|
+
# sema.reach_analysis after analysis completes; consumed by codegen
|
|
656
|
+
# to drive transitive include emission.
|
|
657
|
+
reached: set[str] = field(default_factory=set)
|
|
658
|
+
|
|
659
|
+
# --- Final globals ---
|
|
660
|
+
final_globals: set[str] = field(default_factory=set)
|
|
661
|
+
analyzed_finals: set[str] = field(default_factory=set)
|
|
662
|
+
|
|
663
|
+
# --- Builtins ---
|
|
664
|
+
builtin_names: dict[str, TpyType] = field(default_factory=dict)
|
|
665
|
+
|
|
666
|
+
# --- Namespace hierarchy (global/builtins persist) ---
|
|
667
|
+
builtins_ns: Namespace | None = None
|
|
668
|
+
macro_ns: Namespace | None = None
|
|
669
|
+
global_ns: Namespace | None = None
|
|
670
|
+
|
|
671
|
+
# --- Macro dep modules (populated after macros run) ---
|
|
672
|
+
macro_dep_modules: set[str] = field(default_factory=set)
|
|
673
|
+
|
|
674
|
+
# --- Recursive union aliases (self- or mutually-referencing) ---
|
|
675
|
+
recursive_union_names: set[str] = field(default_factory=set)
|
|
676
|
+
|
|
677
|
+
# --- Module resolver (parser-owned) ---
|
|
678
|
+
# The `TypeResolver` the parser built for this module. Wired by
|
|
679
|
+
# `sema.analyzer.analyze()` from `module.resolver`. Consumed by:
|
|
680
|
+
# - `_infer_field_type_from_default` to look up same-module
|
|
681
|
+
# records via the parser's registry (not yet in `ctx.registry`
|
|
682
|
+
# when the field-default pass runs).
|
|
683
|
+
# - `register_record`'s macro post-resolve step (resolves
|
|
684
|
+
# TypeRefNodes in bodies of methods added by class macros,
|
|
685
|
+
# which join the record after the module-level `resolve_refs`
|
|
686
|
+
# walk has already finished).
|
|
687
|
+
# No other sema code should touch this; the main parser ->
|
|
688
|
+
# TpyType binding happens via `parse.resolve_refs.resolve_refs`.
|
|
689
|
+
parser_resolver: 'TypeResolver | None' = None
|
|
690
|
+
|
|
691
|
+
# --- Control flow (persistent) ---
|
|
692
|
+
in_comprehension: int = 0
|
|
693
|
+
sc_and_walrus: set[str] = field(default_factory=set)
|
|
694
|
+
sc_or_walrus: set[str] = field(default_factory=set)
|
|
695
|
+
try_except_error_type: str | None = None
|
|
696
|
+
in_except_tier: Literal["return", "throw"] | None = None
|
|
697
|
+
# Whether the current except handler has an 'as e' binding (needed for return-tier re-raise)
|
|
698
|
+
in_except_has_binding: bool = False
|
|
699
|
+
# True when analyzing a finally body (raise is not allowed there)
|
|
700
|
+
in_finally: bool = False
|
|
701
|
+
is_top_level: bool = False
|
|
702
|
+
# REPL mode: allow @error_return calls at top level (unwrap with panic)
|
|
703
|
+
allow_top_level_error_unwrap: bool = False
|
|
704
|
+
# Re-entry guard for `is_type_nocopy` on a `RecursiveAliasInstanceType`:
|
|
705
|
+
# the wrapper's own self-reference (`list[Tree[T]]` member) would otherwise
|
|
706
|
+
# recurse into Tree[T] forever. Conservative False at the recursion point
|
|
707
|
+
# mirrors `RecursiveAliasInstanceType.is_value_type`'s guard in typesys.
|
|
708
|
+
_evaluating_alias_nocopy: set = field(default_factory=set)
|
|
709
|
+
|
|
710
|
+
# --- Last-use tracking (shared with codegen, persists across functions) ---
|
|
711
|
+
all_last_uses: set[int] = field(default_factory=set)
|
|
712
|
+
|
|
713
|
+
# --- Consuming method tracking ---
|
|
714
|
+
in_consuming_method: bool = False
|
|
715
|
+
|
|
716
|
+
# --- Expression type hint ---
|
|
717
|
+
expr_type_hint: TpyType | None = None
|
|
718
|
+
|
|
719
|
+
# --- Branch-declared variable tracking ---
|
|
720
|
+
if_branch_decls: dict[int, dict[str, TpyType]] = field(default_factory=dict)
|
|
721
|
+
|
|
722
|
+
# --- Extern symbol tracking ---
|
|
723
|
+
extern_symbols: dict[str, str] = field(default_factory=dict)
|
|
724
|
+
|
|
725
|
+
# --- Builder-trace fresh-name counters ---
|
|
726
|
+
# hint -> next index. Shared across BuilderTraceExpander instances
|
|
727
|
+
# so two functions that each open an ArgumentParser trace don't
|
|
728
|
+
# both mint __tpy_builder_argparse_args_1, which would collide as
|
|
729
|
+
# a top-level record/function name in the synthesized module.
|
|
730
|
+
builder_trace_fresh_counters: dict[str, int] = field(default_factory=dict)
|
|
731
|
+
|
|
732
|
+
# --- Diagnostics ---
|
|
733
|
+
diagnostics: list[Diagnostic] = field(default_factory=list)
|
|
734
|
+
|
|
735
|
+
def is_readonly_name(self, name: str) -> bool:
|
|
736
|
+
"""Check if a variable has readonly provenance in its declared scope type.
|
|
737
|
+
|
|
738
|
+
isinstance narrowing updates narrowed_types (not scope bindings), so the
|
|
739
|
+
scope binding preserves ReadonlyType even when the expr type is narrowed
|
|
740
|
+
to a concrete member. Reassignment updates the scope binding, so a
|
|
741
|
+
non-readonly reassignment correctly clears this.
|
|
742
|
+
"""
|
|
743
|
+
if self.func.current_scope is None:
|
|
744
|
+
return False
|
|
745
|
+
declared = self.func.current_scope.lookup(name)
|
|
746
|
+
return declared is not None and isinstance(declared, ReadonlyType)
|
|
747
|
+
|
|
748
|
+
def _resolve_loc(self, node: TpyExpr | TpyStmt | TpyRecord | None) -> SourceLocation | None:
|
|
749
|
+
"""Resolve source location from a node, with fallback to current function/record."""
|
|
750
|
+
loc = getattr(node, 'loc', None) if node else None
|
|
751
|
+
if loc is None and isinstance(self.func.current_function, TpyFunction):
|
|
752
|
+
loc = self.func.current_function.loc
|
|
753
|
+
if loc is None and self.record_ctx.record is not None:
|
|
754
|
+
loc = self.record_ctx.record.loc
|
|
755
|
+
return loc
|
|
756
|
+
|
|
757
|
+
def error(self, message: str, node: TpyExpr | TpyStmt | None = None) -> SemanticError:
|
|
758
|
+
"""Create a SemanticError with location from a node."""
|
|
759
|
+
return SemanticError(message, self._resolve_loc(node))
|
|
760
|
+
|
|
761
|
+
def emit_error(self, message: str, node: TpyExpr | TpyStmt | None = None) -> None:
|
|
762
|
+
"""Record an error diagnostic without raising (allows continued analysis)."""
|
|
763
|
+
self.diagnostics.append(Diagnostic(DiagnosticLevel.ERROR, message, self._resolve_loc(node)))
|
|
764
|
+
|
|
765
|
+
def warning(self, message: str, node: TpyExpr | TpyStmt | TpyRecord | None = None) -> None:
|
|
766
|
+
"""Record a warning diagnostic (doesn't stop compilation)."""
|
|
767
|
+
self.diagnostics.append(Diagnostic(DiagnosticLevel.WARNING, message, self._resolve_loc(node)))
|
|
768
|
+
|
|
769
|
+
def warning_from_loc(self, message: str, loc: 'SourceLocation | None') -> None:
|
|
770
|
+
"""Record a warning diagnostic from a SourceLocation."""
|
|
771
|
+
self.diagnostics.append(Diagnostic(DiagnosticLevel.WARNING, message, loc))
|
|
772
|
+
|
|
773
|
+
def nocopy_reason(self, typ: TpyType) -> str:
|
|
774
|
+
"""Return a human-readable reason why a type is nocopy.
|
|
775
|
+
|
|
776
|
+
For explicitly @nocopy types returns "@nocopy type 'X'".
|
|
777
|
+
For implicitly nocopy types (propagated from fields) returns a message
|
|
778
|
+
explaining which field caused it.
|
|
779
|
+
"""
|
|
780
|
+
inner = unwrap_readonly(typ)
|
|
781
|
+
if isinstance(inner, OwnType):
|
|
782
|
+
inner = inner.wrapped
|
|
783
|
+
record = self.registry.get_record_for_type(inner)
|
|
784
|
+
if record is None:
|
|
785
|
+
return f"non-copyable type '{typ}'"
|
|
786
|
+
# Walk fields to find the nocopy one (implicitly propagated)
|
|
787
|
+
for f in record.fields:
|
|
788
|
+
if self.is_type_nocopy(f.type):
|
|
789
|
+
return (
|
|
790
|
+
f"non-copyable type '{typ}' (field '{f.name}' "
|
|
791
|
+
f"has non-copyable type '{f.type}')"
|
|
792
|
+
)
|
|
793
|
+
# Check type arguments for generic instantiations
|
|
794
|
+
if isinstance(inner, NominalType) and inner.type_args:
|
|
795
|
+
if record is None or not record.has_copy:
|
|
796
|
+
for arg in inner.type_args:
|
|
797
|
+
if isinstance(arg, TpyType) and self.is_type_nocopy(arg):
|
|
798
|
+
return (
|
|
799
|
+
f"non-copyable type '{typ}' (type argument "
|
|
800
|
+
f"'{arg}' is non-copyable)"
|
|
801
|
+
)
|
|
802
|
+
# Check parents
|
|
803
|
+
for p in record.parents:
|
|
804
|
+
parent_rec = self.registry.get_record_for_type(p)
|
|
805
|
+
if parent_rec is not None and parent_rec.is_nocopy:
|
|
806
|
+
return (
|
|
807
|
+
f"non-copyable type '{typ}' (parent '{p}' "
|
|
808
|
+
f"is non-copyable)"
|
|
809
|
+
)
|
|
810
|
+
# Explicitly decorated with @nocopy (or builtin nocopy)
|
|
811
|
+
return f"@nocopy type '{typ}'"
|
|
812
|
+
|
|
813
|
+
def is_type_nocopy(self, typ: TpyType) -> bool:
|
|
814
|
+
"""Check if a type is nocopy, recursively unwrapping wrappers and generics.
|
|
815
|
+
|
|
816
|
+
For generic instantiations like Box[NocopyType], checks whether any
|
|
817
|
+
type argument is nocopy. Respects __copy__ escape hatch: if the
|
|
818
|
+
containing record defines __copy__, type-arg nocopy is suppressed.
|
|
819
|
+
"""
|
|
820
|
+
if isinstance(typ, ReadonlyType):
|
|
821
|
+
return self.is_type_nocopy(typ.wrapped)
|
|
822
|
+
if isinstance(typ, OwnType):
|
|
823
|
+
return self.is_type_nocopy(typ.wrapped)
|
|
824
|
+
if isinstance(typ, OptionalType):
|
|
825
|
+
return self.is_type_nocopy(typ.inner)
|
|
826
|
+
if isinstance(typ, TupleType):
|
|
827
|
+
for e in typ.element_types:
|
|
828
|
+
if isinstance(e, TpyType) and self.is_type_nocopy(e):
|
|
829
|
+
return True
|
|
830
|
+
return False
|
|
831
|
+
# Generic recursive alias instance: a @nocopy alternative makes the
|
|
832
|
+
# wrapper's std::variant non-copyable. Mirrors the TupleType walk
|
|
833
|
+
# above; uses the unified alternatives accessor. The recursive
|
|
834
|
+
# alternative (e.g. `list[Tree[T]]`) re-enters Tree[T] -- conservative
|
|
835
|
+
# False at the recursion point matches `is_value_type`'s guard.
|
|
836
|
+
if isinstance(typ, RecursiveAliasInstanceType):
|
|
837
|
+
key = (typ.qname, typ.type_args)
|
|
838
|
+
if key in self._evaluating_alias_nocopy:
|
|
839
|
+
return False
|
|
840
|
+
self._evaluating_alias_nocopy.add(key)
|
|
841
|
+
try:
|
|
842
|
+
for m in (recursive_union_alternatives(typ) or ()):
|
|
843
|
+
if isinstance(m, TpyType) and self.is_type_nocopy(m):
|
|
844
|
+
return True
|
|
845
|
+
return False
|
|
846
|
+
finally:
|
|
847
|
+
self._evaluating_alias_nocopy.discard(key)
|
|
848
|
+
record = self.registry.get_record_for_type(typ)
|
|
849
|
+
if record is not None and record.is_nocopy:
|
|
850
|
+
return True
|
|
851
|
+
if isinstance(typ, NominalType) and typ.type_args:
|
|
852
|
+
if record is not None and record.has_copy:
|
|
853
|
+
return False
|
|
854
|
+
for arg in typ.type_args:
|
|
855
|
+
if isinstance(arg, TpyType) and self.is_type_nocopy(arg):
|
|
856
|
+
return True
|
|
857
|
+
return False
|
|
858
|
+
|
|
859
|
+
def is_type_non_copyable(self, typ: TpyType) -> bool:
|
|
860
|
+
"""Check if a type cannot be copied at C++ level.
|
|
861
|
+
|
|
862
|
+
Superset of is_type_nocopy: also counts records with __del__ (which
|
|
863
|
+
deletes copy ops in the generated struct), and walks the parent
|
|
864
|
+
chain for @nocopy or __del__ (which implicitly deletes copy in
|
|
865
|
+
the child). Propagates through record fields and type args.
|
|
866
|
+
Respects __copy__ escape hatch.
|
|
867
|
+
|
|
868
|
+
Used to upgrade "copies X into field/container" warnings to errors --
|
|
869
|
+
the generated C++ would otherwise hit a deleted copy ctor.
|
|
870
|
+
|
|
871
|
+
Kept separate from is_type_nocopy because the latter feeds into
|
|
872
|
+
analyzer-level `is_nocopy` propagation and user-facing "@nocopy"
|
|
873
|
+
diagnostics, where the narrower "user declared intent" meaning is
|
|
874
|
+
wanted. This helper is strictly about "will C++ reject the copy?".
|
|
875
|
+
"""
|
|
876
|
+
if isinstance(typ, ReadonlyType):
|
|
877
|
+
return self.is_type_non_copyable(typ.wrapped)
|
|
878
|
+
if isinstance(typ, OwnType):
|
|
879
|
+
return self.is_type_non_copyable(typ.wrapped)
|
|
880
|
+
if isinstance(typ, OptionalType):
|
|
881
|
+
return self.is_type_non_copyable(typ.inner)
|
|
882
|
+
if isinstance(typ, TupleType):
|
|
883
|
+
for e in typ.element_types:
|
|
884
|
+
if isinstance(e, TpyType) and self.is_type_non_copyable(e):
|
|
885
|
+
return True
|
|
886
|
+
return False
|
|
887
|
+
# Mirrors the is_type_nocopy branch: a non-copyable alternative
|
|
888
|
+
# propagates through the wrapper's std::variant. Re-entry returns
|
|
889
|
+
# False to match the value/nocopy recursion guards (the recursive
|
|
890
|
+
# alternative re-enters the same instance).
|
|
891
|
+
if isinstance(typ, RecursiveAliasInstanceType):
|
|
892
|
+
key = (typ.qname, typ.type_args)
|
|
893
|
+
if key in self._evaluating_alias_nocopy:
|
|
894
|
+
return False
|
|
895
|
+
self._evaluating_alias_nocopy.add(key)
|
|
896
|
+
try:
|
|
897
|
+
for m in (recursive_union_alternatives(typ) or ()):
|
|
898
|
+
if isinstance(m, TpyType) and self.is_type_non_copyable(m):
|
|
899
|
+
return True
|
|
900
|
+
return False
|
|
901
|
+
finally:
|
|
902
|
+
self._evaluating_alias_nocopy.discard(key)
|
|
903
|
+
# Abstract @dynamic protocol bases have pure virtuals and the
|
|
904
|
+
# concrete size depends on the dynamic type, so they have no usable
|
|
905
|
+
# copy/move ctor at the C++ level.
|
|
906
|
+
if is_dyn_protocol(typ):
|
|
907
|
+
return True
|
|
908
|
+
record = self.registry.get_record_for_type(typ)
|
|
909
|
+
if record is None:
|
|
910
|
+
return False
|
|
911
|
+
if record.has_copy:
|
|
912
|
+
return False
|
|
913
|
+
if record.is_nocopy or record.has_del:
|
|
914
|
+
return True
|
|
915
|
+
# Inheritance: recurse into each direct parent so each parent's own logic
|
|
916
|
+
# (including its has_copy barrier up its chain) applies independently.
|
|
917
|
+
# For multi-base, any direct parent being non-copyable makes the child
|
|
918
|
+
# non-copyable; for single-parent, parent.has_copy correctly short-circuits
|
|
919
|
+
# the parent's recursion, matching the old `break`-on-has_copy behavior.
|
|
920
|
+
for p in record.parents:
|
|
921
|
+
if self.is_type_non_copyable(p):
|
|
922
|
+
return True
|
|
923
|
+
if isinstance(typ, NominalType) and typ.type_args:
|
|
924
|
+
for arg in typ.type_args:
|
|
925
|
+
if isinstance(arg, TpyType) and self.is_type_non_copyable(arg):
|
|
926
|
+
return True
|
|
927
|
+
for f in record.fields:
|
|
928
|
+
if self.is_type_non_copyable(f.type):
|
|
929
|
+
return True
|
|
930
|
+
return False
|
|
931
|
+
|
|
932
|
+
def get_expr_type(self, expr: TpyExpr) -> TpyType | None:
|
|
933
|
+
"""Get the cached type of an expression, stripping Ref and Own.
|
|
934
|
+
|
|
935
|
+
Ref/Own are internal annotations for reference provenance and
|
|
936
|
+
ownership; callers that need bare types (codegen, type resolution,
|
|
937
|
+
narrowing) should not see them. ReadonlyType is preserved because
|
|
938
|
+
codegen needs it for const emission. The assignment handler uses
|
|
939
|
+
the analyze_expr return value directly (which preserves Ref/Own)
|
|
940
|
+
for copy-warning detection.
|
|
941
|
+
"""
|
|
942
|
+
typ = self.expr_types.get(id(expr))
|
|
943
|
+
if typ is None:
|
|
944
|
+
return None
|
|
945
|
+
typ = unwrap_ref_type(typ)
|
|
946
|
+
if isinstance(typ, OwnType):
|
|
947
|
+
typ = typ.wrapped
|
|
948
|
+
return typ
|
|
949
|
+
|
|
950
|
+
def get_raw_expr_type(self, expr: TpyExpr) -> TpyType | None:
|
|
951
|
+
"""Get the cached type of an expression WITHOUT stripping qualifiers.
|
|
952
|
+
|
|
953
|
+
Used by copy-warning detection to see Ref/Own qualifiers.
|
|
954
|
+
"""
|
|
955
|
+
return self.expr_types.get(id(expr))
|
|
956
|
+
|
|
957
|
+
def set_expr_type(self, expr: TpyExpr, typ: TpyType) -> None:
|
|
958
|
+
"""Cache the type of an expression."""
|
|
959
|
+
self.expr_types[id(expr)] = typ
|
|
960
|
+
|
|
961
|
+
def default_int_for_literal(
|
|
962
|
+
self,
|
|
963
|
+
typ: TpyType,
|
|
964
|
+
warn_node: TpyExpr | TpyStmt | None = None,
|
|
965
|
+
) -> TpyType:
|
|
966
|
+
"""Resolve configured default-int, with range-safe fallback for literals."""
|
|
967
|
+
if not isinstance(typ, IntLiteralType):
|
|
968
|
+
return self.default_int_type
|
|
969
|
+
default_tr = int_traits_of(self.default_int_type)
|
|
970
|
+
if (
|
|
971
|
+
default_tr is not None
|
|
972
|
+
and typ.value is not None
|
|
973
|
+
and not (default_tr.min_value <= typ.value <= default_tr.max_value)
|
|
974
|
+
):
|
|
975
|
+
if warn_node is not None:
|
|
976
|
+
self.warning(
|
|
977
|
+
f"Integer literal {typ.value} is outside default {self.default_int_type} range; "
|
|
978
|
+
"inferring int (BigInt).",
|
|
979
|
+
warn_node,
|
|
980
|
+
)
|
|
981
|
+
return BIGINT
|
|
982
|
+
return self.default_int_type
|
|
983
|
+
|
|
984
|
+
def reset_function_tracking(self) -> None:
|
|
985
|
+
"""Reset all per-function tracking state."""
|
|
986
|
+
self.func = FunctionTrackingState()
|
|
987
|
+
|
|
988
|
+
def save_function_state(self) -> FunctionTrackingState:
|
|
989
|
+
"""Snapshot per-function state (for nested def isolation)."""
|
|
990
|
+
return deepcopy(self.func)
|
|
991
|
+
|
|
992
|
+
def restore_function_state(self, saved: FunctionTrackingState) -> None:
|
|
993
|
+
"""Restore per-function state from a snapshot."""
|
|
994
|
+
self.func = saved
|
|
995
|
+
|
|
996
|
+
@contextmanager
|
|
997
|
+
def trial_scope(self) -> Iterator[None]:
|
|
998
|
+
"""Snapshot/restore for tentative semantic analysis (e.g. per-candidate
|
|
999
|
+
lambda body trial under Regime C overload resolution).
|
|
1000
|
+
|
|
1001
|
+
Wraps a body of analysis work whose state changes must be rolled back
|
|
1002
|
+
unconditionally on exit -- both on success and on exception.
|
|
1003
|
+
|
|
1004
|
+
Snapshotted surfaces:
|
|
1005
|
+
|
|
1006
|
+
- Per-function state (FunctionTrackingState) -- includes call_edges,
|
|
1007
|
+
mutated/struct/addr-escape/returned param sets, self-mutation flags,
|
|
1008
|
+
borrow tracker, definitely_assigned, narrowed_types, etc. Deep-copied
|
|
1009
|
+
since FunctionTrackingState contains nested mutable containers.
|
|
1010
|
+
- Module-level type cache (``expr_types``) and the literal/view
|
|
1011
|
+
counters and registries (``literal_counter``, ``list_literals``,
|
|
1012
|
+
``dict_literals``, ``set_literals``, ``pending_generic_counter``,
|
|
1013
|
+
``str_var_counter``, ``str_vars``, ``bytes_var_counter``,
|
|
1014
|
+
``bytes_vars``).
|
|
1015
|
+
- The diagnostics list -- truncated to its pre-trial length so
|
|
1016
|
+
warnings/errors emitted during a rejected trial don't leak into
|
|
1017
|
+
the user-visible output.
|
|
1018
|
+
|
|
1019
|
+
Lambda AST mutations (``inferred_param_types``, ``inferred_return_type``,
|
|
1020
|
+
``captured_names``, ``captures_by_value``) are the caller's
|
|
1021
|
+
responsibility -- save and restore them around the trial. They are
|
|
1022
|
+
not part of SemanticContext state.
|
|
1023
|
+
"""
|
|
1024
|
+
saved_func = self.save_function_state()
|
|
1025
|
+
saved_expr_types = dict(self.expr_types)
|
|
1026
|
+
saved_literal_counter = self.literal_counter
|
|
1027
|
+
saved_list_literals = dict(self.list_literals)
|
|
1028
|
+
saved_dict_literals = dict(self.dict_literals)
|
|
1029
|
+
saved_set_literals = dict(self.set_literals)
|
|
1030
|
+
saved_pending_generic_counter = self.pending_generic_counter
|
|
1031
|
+
saved_str_var_counter = self.str_var_counter
|
|
1032
|
+
saved_str_vars = dict(self.str_vars)
|
|
1033
|
+
saved_bytes_var_counter = self.bytes_var_counter
|
|
1034
|
+
saved_bytes_vars = dict(self.bytes_vars)
|
|
1035
|
+
saved_diagnostics_len = len(self.diagnostics)
|
|
1036
|
+
try:
|
|
1037
|
+
yield
|
|
1038
|
+
finally:
|
|
1039
|
+
self.restore_function_state(saved_func)
|
|
1040
|
+
self.expr_types.clear()
|
|
1041
|
+
self.expr_types.update(saved_expr_types)
|
|
1042
|
+
self.literal_counter = saved_literal_counter
|
|
1043
|
+
self.list_literals.clear()
|
|
1044
|
+
self.list_literals.update(saved_list_literals)
|
|
1045
|
+
self.dict_literals.clear()
|
|
1046
|
+
self.dict_literals.update(saved_dict_literals)
|
|
1047
|
+
self.set_literals.clear()
|
|
1048
|
+
self.set_literals.update(saved_set_literals)
|
|
1049
|
+
self.pending_generic_counter = saved_pending_generic_counter
|
|
1050
|
+
self.str_var_counter = saved_str_var_counter
|
|
1051
|
+
self.str_vars.clear()
|
|
1052
|
+
self.str_vars.update(saved_str_vars)
|
|
1053
|
+
self.bytes_var_counter = saved_bytes_var_counter
|
|
1054
|
+
self.bytes_vars.clear()
|
|
1055
|
+
self.bytes_vars.update(saved_bytes_vars)
|
|
1056
|
+
del self.diagnostics[saved_diagnostics_len:]
|
|
1057
|
+
|
|
1058
|
+
def mark_loop_var_mutated(self, name: str) -> None:
|
|
1059
|
+
"""Mark a for-each loop variable as mutated (prevents const-ref binding)."""
|
|
1060
|
+
if name in self.func.loop_vars:
|
|
1061
|
+
self.func.mutated_loop_vars.add(name)
|
|
1062
|
+
|
|
1063
|
+
def mark_loop_var_consumed(self, name: str) -> None:
|
|
1064
|
+
"""Mark a for-each loop variable as consumed (copied into owned storage).
|
|
1065
|
+
|
|
1066
|
+
This triggers auto-consuming iteration when the container is at last
|
|
1067
|
+
use, so elements are moved instead of copied.
|
|
1068
|
+
"""
|
|
1069
|
+
if name in self.func.loop_vars:
|
|
1070
|
+
self.func.consumed_loop_vars.add(name)
|
|
1071
|
+
|
|
1072
|
+
def mark_param_mutated(self, name: str) -> None:
|
|
1073
|
+
"""Mark a function parameter as directly mutated (Phase 1 of mutation inference).
|
|
1074
|
+
|
|
1075
|
+
For 'self': sets current_self_mutated (method self-mutation tracking).
|
|
1076
|
+
For regular params: adds to current_mutated_param_names.
|
|
1077
|
+
Also traces loop variables back to their source iterables, and traces
|
|
1078
|
+
element/field/ptr borrows back to their source storage (8a.5: deferred
|
|
1079
|
+
marking for element refs -- when v = items[i] and v.field is written,
|
|
1080
|
+
the write propagates back to items).
|
|
1081
|
+
"""
|
|
1082
|
+
if name == "self":
|
|
1083
|
+
self.func.current_self_mutated = True
|
|
1084
|
+
return
|
|
1085
|
+
if name in self.func.current_param_names and name not in self.func.current_rebound_params:
|
|
1086
|
+
self.func.current_mutated_param_names.add(name)
|
|
1087
|
+
iterable = self.func.loop_var_iterable.get(name)
|
|
1088
|
+
if iterable is not None:
|
|
1089
|
+
# Field-path iterables ("c.items") need root extraction for param lookup
|
|
1090
|
+
self.mark_param_mutated(_storage_root(iterable))
|
|
1091
|
+
# 8a.5: trace through element/field/ptr borrows to source param.
|
|
1092
|
+
# When v = items[i] (deferred) and v is later written through,
|
|
1093
|
+
# mark the ultimate storage root (e.g. items) as mutated.
|
|
1094
|
+
# The recursive call terminates because effective_storage_through_borrows
|
|
1095
|
+
# on the ultimate root returns itself (no upstream borrow points to it).
|
|
1096
|
+
ultimate = self.func.borrow_tracker.effective_storage_through_borrows(name)
|
|
1097
|
+
if ultimate != name:
|
|
1098
|
+
self.mark_param_mutated(ultimate)
|
|
1099
|
+
|
|
1100
|
+
def mark_param_structurally_mutated(self, name: str) -> None:
|
|
1101
|
+
"""Mark a parameter as structurally mutated (append/insert/clear/del/etc.).
|
|
1102
|
+
|
|
1103
|
+
Structural mutations invalidate element references -- this is separate from
|
|
1104
|
+
mark_param_mutated which also fires for element-ref taking (a = items[0]).
|
|
1105
|
+
Traces loop variables back to their source iterables transitively.
|
|
1106
|
+
"""
|
|
1107
|
+
if name == "self":
|
|
1108
|
+
self.func.current_self_struct_mutated = True
|
|
1109
|
+
return
|
|
1110
|
+
if name in self.func.current_param_names and name not in self.func.current_rebound_params:
|
|
1111
|
+
self.func.current_struct_mutated_param_names.add(name)
|
|
1112
|
+
iterable = self.func.loop_var_iterable.get(name)
|
|
1113
|
+
if iterable is not None:
|
|
1114
|
+
self.mark_param_structurally_mutated(_storage_root(iterable))
|
|
1115
|
+
|
|
1116
|
+
def mark_param_returned(self, name: str) -> None:
|
|
1117
|
+
"""Mark a parameter as contributing to the return value (8b).
|
|
1118
|
+
|
|
1119
|
+
Called when returning a reference derived from param storage, so we
|
|
1120
|
+
can record return_borrows_from on FunctionInfo. Mirrors mark_param_mutated
|
|
1121
|
+
but writes to current_returned_param_names instead.
|
|
1122
|
+
Traces loop variables back to their source iterables transitively.
|
|
1123
|
+
"""
|
|
1124
|
+
if name == "self":
|
|
1125
|
+
self.func.current_returned_param_names.add("self")
|
|
1126
|
+
return
|
|
1127
|
+
if name in self.func.current_param_names and name not in self.func.current_rebound_params:
|
|
1128
|
+
self.func.current_returned_param_names.add(name)
|
|
1129
|
+
iterable = self.func.loop_var_iterable.get(name)
|
|
1130
|
+
if iterable is not None:
|
|
1131
|
+
self.mark_param_returned(_storage_root(iterable))
|
|
1132
|
+
|
|
1133
|
+
def mark_own_param_consumed(self, name: str) -> None:
|
|
1134
|
+
"""Mark an Own[T] param as consumed (stored, forwarded, or returned)."""
|
|
1135
|
+
if name in self.func.current_param_names:
|
|
1136
|
+
self.func.current_consumed_own_params.add(name)
|
|
1137
|
+
|
|
1138
|
+
# ------------------------------------------------------------------
|
|
1139
|
+
# View-type family generic accessors
|
|
1140
|
+
# ------------------------------------------------------------------
|
|
1141
|
+
|
|
1142
|
+
def view_var_map(self, family: ViewTypeFamily) -> dict[str, int]:
|
|
1143
|
+
"""Variable-name -> var_id mapping for the given family."""
|
|
1144
|
+
if family.pending_type_class is PendingStrType:
|
|
1145
|
+
return self.func.variable_to_str_var
|
|
1146
|
+
return self.func.variable_to_bytes_var
|
|
1147
|
+
|
|
1148
|
+
def view_source_borrows_map(self, family: ViewTypeFamily) -> dict[str, set[int]]:
|
|
1149
|
+
"""Source-storage -> set of borrowing var_ids for the given family."""
|
|
1150
|
+
if family.pending_type_class is PendingStrType:
|
|
1151
|
+
return self.func.str_source_borrows
|
|
1152
|
+
return self.func.bytes_source_borrows
|
|
1153
|
+
|
|
1154
|
+
def view_pending_resolutions(self, family: ViewTypeFamily) -> list[int]:
|
|
1155
|
+
"""Pending resolution list for the given family."""
|
|
1156
|
+
if family.pending_type_class is PendingStrType:
|
|
1157
|
+
return self.func.pending_str_resolutions
|
|
1158
|
+
return self.func.pending_bytes_resolutions
|
|
1159
|
+
|
|
1160
|
+
def view_vars(self, family: ViewTypeFamily) -> dict[int, ViewVarInfo]:
|
|
1161
|
+
"""Var-id -> ViewVarInfo registry for the given family."""
|
|
1162
|
+
if family.pending_type_class is PendingStrType:
|
|
1163
|
+
return self.str_vars
|
|
1164
|
+
return self.bytes_vars
|
|
1165
|
+
|
|
1166
|
+
def next_view_var_id(self, family: ViewTypeFamily) -> int:
|
|
1167
|
+
"""Allocate and return the next var_id for the given family."""
|
|
1168
|
+
if family.pending_type_class is PendingStrType:
|
|
1169
|
+
vid = self.str_var_counter
|
|
1170
|
+
self.str_var_counter += 1
|
|
1171
|
+
return vid
|
|
1172
|
+
vid = self.bytes_var_counter
|
|
1173
|
+
self.bytes_var_counter += 1
|
|
1174
|
+
return vid
|
|
1175
|
+
|
|
1176
|
+
# ------------------------------------------------------------------
|
|
1177
|
+
# View-type mutation tracking
|
|
1178
|
+
# ------------------------------------------------------------------
|
|
1179
|
+
|
|
1180
|
+
def mark_view_borrowers_mutated(self, storage: str, family: ViewTypeFamily) -> None:
|
|
1181
|
+
"""Mark pending view-type borrowers of storage as source-mutated.
|
|
1182
|
+
|
|
1183
|
+
Called at mutation sites so that view resolution falls back to
|
|
1184
|
+
the owned type when the view's source storage is mutated.
|
|
1185
|
+
Also invalidates field-path borrows: mutating ``p`` invalidates
|
|
1186
|
+
views borrowed from ``p.name``, ``p.field``, etc.
|
|
1187
|
+
"""
|
|
1188
|
+
source_borrows = self.view_source_borrows_map(family)
|
|
1189
|
+
vars_registry = self.view_vars(family)
|
|
1190
|
+
keys = [storage]
|
|
1191
|
+
prefix = storage + "."
|
|
1192
|
+
keys.extend(k for k in source_borrows if k.startswith(prefix))
|
|
1193
|
+
for key in keys:
|
|
1194
|
+
var_ids = source_borrows.get(key)
|
|
1195
|
+
if not var_ids:
|
|
1196
|
+
continue
|
|
1197
|
+
for var_id in var_ids:
|
|
1198
|
+
info = vars_registry.get(var_id)
|
|
1199
|
+
if info is not None:
|
|
1200
|
+
info.source_mutated = True
|
|
1201
|
+
|
|
1202
|
+
def mark_all_view_borrowers_mutated(self, storage: str) -> None:
|
|
1203
|
+
"""Mark borrowers across all view-type families as source-mutated."""
|
|
1204
|
+
for family in VIEW_TYPE_FAMILIES:
|
|
1205
|
+
self.mark_view_borrowers_mutated(storage, family)
|
|
1206
|
+
|
|
1207
|
+
# ------------------------------------------------------------------
|
|
1208
|
+
# Unified container literal lookup
|
|
1209
|
+
# ------------------------------------------------------------------
|
|
1210
|
+
|
|
1211
|
+
def get_container_info(self, literal_id: int) -> ListLiteralInfo | DictLiteralInfo | SetLiteralInfo | None:
|
|
1212
|
+
"""Look up container info across all container types by literal_id."""
|
|
1213
|
+
return (self.list_literals.get(literal_id)
|
|
1214
|
+
or self.dict_literals.get(literal_id)
|
|
1215
|
+
or self.set_literals.get(literal_id))
|
|
1216
|
+
|
|
1217
|
+
def track_container_variable(
|
|
1218
|
+
self,
|
|
1219
|
+
var_name: str,
|
|
1220
|
+
pending_type: 'PendingListType | PendingDictType | PendingSetType',
|
|
1221
|
+
decl_line: int | None,
|
|
1222
|
+
) -> None:
|
|
1223
|
+
"""Register var-name -> literal_id mapping for any pending container type.
|
|
1224
|
+
|
|
1225
|
+
Also sets variable_name and decl_line on the corresponding LiteralInfo.
|
|
1226
|
+
Single code path for list/dict/set -- adding a new container type means
|
|
1227
|
+
adding one branch here instead of duplicating blocks in statements.py.
|
|
1228
|
+
"""
|
|
1229
|
+
literal_id = pending_type.literal_id
|
|
1230
|
+
info: ListLiteralInfo | DictLiteralInfo | SetLiteralInfo
|
|
1231
|
+
if isinstance(pending_type, PendingListType):
|
|
1232
|
+
self.func.variable_to_literal[var_name] = literal_id
|
|
1233
|
+
info = self.list_literals[literal_id]
|
|
1234
|
+
elif isinstance(pending_type, PendingDictType):
|
|
1235
|
+
self.func.variable_to_dict_literal[var_name] = literal_id
|
|
1236
|
+
info = self.dict_literals[literal_id]
|
|
1237
|
+
elif isinstance(pending_type, PendingSetType):
|
|
1238
|
+
self.func.variable_to_set_literal[var_name] = literal_id
|
|
1239
|
+
info = self.set_literals[literal_id]
|
|
1240
|
+
else:
|
|
1241
|
+
return
|
|
1242
|
+
info.variable_name = var_name
|
|
1243
|
+
info.decl_line = decl_line
|