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/liveness.py
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
"""Last-use liveness analysis for auto-move optimization.
|
|
2
|
+
|
|
3
|
+
Pure AST walking -- no type registry or analysis context needed.
|
|
4
|
+
Run once per function/method in sema; results consumed by both sema and codegen.
|
|
5
|
+
|
|
6
|
+
The analysis identifies TpyName nodes that are the "last use" of a variable --
|
|
7
|
+
meaning the variable is not read again on any subsequent execution path (without
|
|
8
|
+
being reassigned first). These sites are candidates for std::move() in codegen.
|
|
9
|
+
|
|
10
|
+
Alias awareness: when alias = h creates a T& reference, auto-move of h is
|
|
11
|
+
suppressed if alias is still live (prevents dangling reference through alias).
|
|
12
|
+
When the source is reassigned (h = new_value), the alias points to old storage
|
|
13
|
+
and no longer constrains moves of the new h (detach-on-reassign).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from .parse import (
|
|
19
|
+
TpyStmt, TpyExpr, TpyVarDecl, TpyTupleUnpack, TpyAssign, TpyAugAssign,
|
|
20
|
+
TpyIf, TpyWhile, TpyForEach, TpyReturn, TpyBreak, TpyRaise,
|
|
21
|
+
TpyMatch, TpyNestedDef, TpyDelVar, TpyTry, TpyWith,
|
|
22
|
+
TpyName, TpyFieldAccess, TpySubscript, TpyNamedExpr,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# source_name -> set[alias_name] reverse map
|
|
26
|
+
_Aliases = dict[str, set[str]]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def analyze_last_uses(
|
|
30
|
+
stmts: list[TpyStmt],
|
|
31
|
+
alias_sources: dict[str, str] | None = None,
|
|
32
|
+
) -> set[int]:
|
|
33
|
+
"""Analyze a function body to find last-use sites for auto-move.
|
|
34
|
+
|
|
35
|
+
Returns a set of id(TpyName) for name nodes that are at their last use --
|
|
36
|
+
the variable is not read again on any subsequent execution path without
|
|
37
|
+
being reassigned first.
|
|
38
|
+
|
|
39
|
+
When alias_sources is provided (alias_name -> source_name), auto-move of
|
|
40
|
+
a source variable is suppressed if any of its T& aliases are still live.
|
|
41
|
+
|
|
42
|
+
The analysis is conservative: if unsure, a node is NOT marked as last use.
|
|
43
|
+
A missed optimization is just a copy (same as current behavior).
|
|
44
|
+
"""
|
|
45
|
+
source_aliases = _build_source_aliases(alias_sources) if alias_sources else {}
|
|
46
|
+
# Per-alias detachment: aliases created before their source's reassignment
|
|
47
|
+
# point to old storage and don't constrain moves of the new value.
|
|
48
|
+
# Re-activated at the reassignment point going backward.
|
|
49
|
+
detached_aliases, first_reassign_pos = (
|
|
50
|
+
_compute_alias_detachment(stmts, source_aliases, alias_sources)
|
|
51
|
+
if alias_sources else (set(), {})
|
|
52
|
+
)
|
|
53
|
+
live: set[str] = set()
|
|
54
|
+
last_uses: set[int] = set()
|
|
55
|
+
_analyze_stmts_backward(
|
|
56
|
+
stmts, live, last_uses, source_aliases,
|
|
57
|
+
detached_aliases, first_reassign_pos,
|
|
58
|
+
)
|
|
59
|
+
return last_uses
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# -- Alias map helpers --------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def _build_source_aliases(alias_sources: dict[str, str]) -> _Aliases:
|
|
65
|
+
"""Build source -> set[alias] reverse map with transitive resolution.
|
|
66
|
+
|
|
67
|
+
Given alias_sources {alias -> source}, resolves chains like
|
|
68
|
+
a -> h, b -> a to a -> h, b -> h, then builds reverse map h -> {a, b}.
|
|
69
|
+
"""
|
|
70
|
+
resolved: dict[str, str] = {}
|
|
71
|
+
for alias in alias_sources:
|
|
72
|
+
source = alias
|
|
73
|
+
visited: set[str] = set()
|
|
74
|
+
while source in alias_sources and source not in visited:
|
|
75
|
+
visited.add(source)
|
|
76
|
+
source = alias_sources[source]
|
|
77
|
+
resolved[alias] = source
|
|
78
|
+
reverse: _Aliases = {}
|
|
79
|
+
for alias, source in resolved.items():
|
|
80
|
+
reverse.setdefault(source, set()).add(alias)
|
|
81
|
+
return reverse
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _has_live_alias(
|
|
85
|
+
name: str, live: set[str], source_aliases: _Aliases,
|
|
86
|
+
detached_aliases: set[str],
|
|
87
|
+
) -> bool:
|
|
88
|
+
"""Check if any active T& alias of 'name' is in the live set.
|
|
89
|
+
|
|
90
|
+
Aliases in detached_aliases were created before their source was reassigned,
|
|
91
|
+
so they point to old storage and don't constrain moves of the new value.
|
|
92
|
+
Only non-detached (active) aliases can suppress auto-move.
|
|
93
|
+
"""
|
|
94
|
+
aliases = source_aliases.get(name)
|
|
95
|
+
if aliases is None:
|
|
96
|
+
return False
|
|
97
|
+
active = aliases - detached_aliases
|
|
98
|
+
return bool(active & live)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _compute_alias_detachment(
|
|
102
|
+
stmts: list[TpyStmt],
|
|
103
|
+
source_aliases: _Aliases,
|
|
104
|
+
alias_sources: dict[str, str],
|
|
105
|
+
) -> tuple[set[str], dict[str, int]]:
|
|
106
|
+
"""Compute per-alias detachment and source reassignment positions.
|
|
107
|
+
|
|
108
|
+
Returns (detached_aliases, first_reassign_pos).
|
|
109
|
+
An alias is "detached" if it was created before the first reassignment
|
|
110
|
+
of its source in the same block -- it points to old storage and doesn't
|
|
111
|
+
constrain moves of the new source value.
|
|
112
|
+
|
|
113
|
+
Only scans direct children (not nested in if/for/etc). Conditional
|
|
114
|
+
reassignments conservatively keep alias checking active.
|
|
115
|
+
"""
|
|
116
|
+
first_reassign_pos: dict[str, int] = {}
|
|
117
|
+
alias_creation_pos: dict[str, int] = {}
|
|
118
|
+
seen: set[str] = set()
|
|
119
|
+
for i, stmt in enumerate(stmts):
|
|
120
|
+
if not isinstance(stmt, TpyVarDecl):
|
|
121
|
+
continue
|
|
122
|
+
name = stmt.name
|
|
123
|
+
# Track source reassignment positions (second+ TpyVarDecl = reassign)
|
|
124
|
+
if name in source_aliases:
|
|
125
|
+
if name in seen:
|
|
126
|
+
if name not in first_reassign_pos:
|
|
127
|
+
first_reassign_pos[name] = i
|
|
128
|
+
else:
|
|
129
|
+
seen.add(name)
|
|
130
|
+
# Track alias creation positions
|
|
131
|
+
if name in alias_sources and name not in alias_creation_pos:
|
|
132
|
+
alias_creation_pos[name] = i
|
|
133
|
+
# An alias is detached only if created BEFORE its source's first reassignment
|
|
134
|
+
detached: set[str] = set()
|
|
135
|
+
for alias, source in alias_sources.items():
|
|
136
|
+
if source in first_reassign_pos and alias in alias_creation_pos:
|
|
137
|
+
if alias_creation_pos[alias] < first_reassign_pos[source]:
|
|
138
|
+
detached.add(alias)
|
|
139
|
+
return detached, first_reassign_pos
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# -- Termination check --------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
def stmts_terminate(stmts: list[TpyStmt]) -> bool:
|
|
145
|
+
"""Do all execution paths through stmts end with return/break/raise?
|
|
146
|
+
|
|
147
|
+
Only inspects the last statement -- relies on the invariant that the
|
|
148
|
+
parser does not emit unreachable statements after a terminator.
|
|
149
|
+
TpyContinue is intentionally excluded: it goes back to the loop header,
|
|
150
|
+
so variables may still be live in subsequent iterations.
|
|
151
|
+
"""
|
|
152
|
+
if not stmts:
|
|
153
|
+
return False
|
|
154
|
+
last = stmts[-1]
|
|
155
|
+
if isinstance(last, (TpyReturn, TpyBreak, TpyRaise)):
|
|
156
|
+
return True
|
|
157
|
+
if isinstance(last, TpyIf):
|
|
158
|
+
return (stmts_terminate(last.then_body)
|
|
159
|
+
and stmts_terminate(last.else_body))
|
|
160
|
+
if isinstance(last, TpyMatch):
|
|
161
|
+
return (bool(last.cases)
|
|
162
|
+
and all(stmts_terminate(case.body) for case in last.cases))
|
|
163
|
+
if isinstance(last, TpyTry):
|
|
164
|
+
# try-finally only (no handlers): try-body terminating is enough.
|
|
165
|
+
# The finally re-throws on exception; a finally body that itself
|
|
166
|
+
# terminates only changes behavior if the try body fell through,
|
|
167
|
+
# which we don't analyze here (conservative: ignore finally-body).
|
|
168
|
+
if not last.handlers:
|
|
169
|
+
return stmts_terminate(last.try_body)
|
|
170
|
+
# try with handlers: terminates iff try-body terminates AND every
|
|
171
|
+
# handler body terminates. else-body (Python try-else) runs when
|
|
172
|
+
# the try body completed normally; if try terminates, else is dead.
|
|
173
|
+
return (stmts_terminate(last.try_body)
|
|
174
|
+
and all(stmts_terminate(h.body) for h in last.handlers))
|
|
175
|
+
if isinstance(last, TpyWith):
|
|
176
|
+
# A suppressing __exit__ can swallow body-raised exceptions and
|
|
177
|
+
# fall through past the `with`, so body-terminates only implies
|
|
178
|
+
# with-terminates when every __exit__ returns None.
|
|
179
|
+
if any(item.exit_can_suppress for item in last.items):
|
|
180
|
+
return False
|
|
181
|
+
return stmts_terminate(last.body)
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# -- Backward analysis --------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
def _analyze_stmts_backward(
|
|
188
|
+
stmts: list[TpyStmt],
|
|
189
|
+
live: set[str],
|
|
190
|
+
last_uses: set[int],
|
|
191
|
+
source_aliases: _Aliases,
|
|
192
|
+
detached_aliases: set[str],
|
|
193
|
+
first_reassign_pos: dict[str, int] | None = None,
|
|
194
|
+
) -> None:
|
|
195
|
+
"""Walk statements backward, updating live set and marking last uses."""
|
|
196
|
+
for i in range(len(stmts) - 1, -1, -1):
|
|
197
|
+
stmt = stmts[i]
|
|
198
|
+
# At the first reassignment of an alias source (going backward),
|
|
199
|
+
# re-activate its aliases for earlier code (before reassignment,
|
|
200
|
+
# aliases DO track the source and must constrain moves).
|
|
201
|
+
if (first_reassign_pos and isinstance(stmt, TpyVarDecl)
|
|
202
|
+
and first_reassign_pos.get(stmt.name) == i):
|
|
203
|
+
aliases = source_aliases.get(stmt.name)
|
|
204
|
+
if aliases:
|
|
205
|
+
detached_aliases -= aliases
|
|
206
|
+
_analyze_stmt(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _analyze_stmt(
|
|
210
|
+
stmt: TpyStmt,
|
|
211
|
+
live: set[str],
|
|
212
|
+
last_uses: set[int],
|
|
213
|
+
source_aliases: _Aliases,
|
|
214
|
+
detached_aliases: set[str],
|
|
215
|
+
) -> None:
|
|
216
|
+
"""Analyze a single statement for last uses."""
|
|
217
|
+
|
|
218
|
+
if isinstance(stmt, TpyIf):
|
|
219
|
+
_analyze_if(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
220
|
+
|
|
221
|
+
elif isinstance(stmt, TpyMatch):
|
|
222
|
+
_analyze_match(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
223
|
+
|
|
224
|
+
elif isinstance(stmt, TpyWhile):
|
|
225
|
+
_analyze_while(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
226
|
+
|
|
227
|
+
elif isinstance(stmt, TpyForEach):
|
|
228
|
+
_analyze_for_each(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
229
|
+
|
|
230
|
+
elif isinstance(stmt, TpyVarDecl):
|
|
231
|
+
# Reads from the init expression
|
|
232
|
+
if stmt.init:
|
|
233
|
+
# For alias creation (alias = source), temporarily hide the alias
|
|
234
|
+
# from the live set so it doesn't suppress the source's last-use.
|
|
235
|
+
# The alias doesn't exist yet at this point in execution -- it's
|
|
236
|
+
# being created by this statement -- so it can't constrain moves.
|
|
237
|
+
hide_alias = (isinstance(stmt.init, TpyName)
|
|
238
|
+
and stmt.name != stmt.init.name
|
|
239
|
+
and stmt.name in live)
|
|
240
|
+
if hide_alias:
|
|
241
|
+
live.discard(stmt.name)
|
|
242
|
+
_process_reads(stmt.init, live, last_uses, source_aliases, detached_aliases)
|
|
243
|
+
if hide_alias:
|
|
244
|
+
live.add(stmt.name)
|
|
245
|
+
# Kill: variable is (re)defined here
|
|
246
|
+
live.discard(stmt.name)
|
|
247
|
+
|
|
248
|
+
elif isinstance(stmt, TpyTupleUnpack):
|
|
249
|
+
_process_reads(stmt.value, live, last_uses, source_aliases, detached_aliases)
|
|
250
|
+
for name in stmt.targets:
|
|
251
|
+
if name is not None:
|
|
252
|
+
live.discard(name)
|
|
253
|
+
|
|
254
|
+
elif isinstance(stmt, TpyAssign):
|
|
255
|
+
# Reads from the value
|
|
256
|
+
_process_reads(stmt.value, live, last_uses, source_aliases, detached_aliases)
|
|
257
|
+
# Reads from target sub-expressions (subscript index, field obj)
|
|
258
|
+
if isinstance(stmt.target, TpySubscript):
|
|
259
|
+
_process_reads(stmt.target.obj, live, last_uses, source_aliases, detached_aliases)
|
|
260
|
+
_process_reads(stmt.target.index, live, last_uses, source_aliases, detached_aliases)
|
|
261
|
+
elif isinstance(stmt.target, TpyFieldAccess):
|
|
262
|
+
_process_reads(stmt.target.obj, live, last_uses, source_aliases, detached_aliases)
|
|
263
|
+
# Kill: if target is a plain name, it's redefined
|
|
264
|
+
elif isinstance(stmt.target, TpyName):
|
|
265
|
+
live.discard(stmt.target.name)
|
|
266
|
+
|
|
267
|
+
elif isinstance(stmt, TpyAugAssign):
|
|
268
|
+
# AugAssign (e.g. x += 1) reads the target AND the value
|
|
269
|
+
_process_reads(stmt.value, live, last_uses, source_aliases, detached_aliases)
|
|
270
|
+
if isinstance(stmt.target, TpySubscript):
|
|
271
|
+
_process_reads(stmt.target.obj, live, last_uses, source_aliases, detached_aliases)
|
|
272
|
+
_process_reads(stmt.target.index, live, last_uses, source_aliases, detached_aliases)
|
|
273
|
+
elif isinstance(stmt.target, TpyFieldAccess):
|
|
274
|
+
_process_reads(stmt.target.obj, live, last_uses, source_aliases, detached_aliases)
|
|
275
|
+
elif isinstance(stmt.target, TpyName):
|
|
276
|
+
# x += val reads x, then writes x
|
|
277
|
+
_process_reads(stmt.target, live, last_uses, source_aliases, detached_aliases)
|
|
278
|
+
# Don't kill: the read happens before the write in the same stmt,
|
|
279
|
+
# and the prescan handles aug-assign separately.
|
|
280
|
+
|
|
281
|
+
elif isinstance(stmt, TpyReturn):
|
|
282
|
+
# Terminating: nothing reached after the return is live. Clear first
|
|
283
|
+
# so the return-expression reads become the only live-before names --
|
|
284
|
+
# otherwise an earlier consume of a var read here is misread as
|
|
285
|
+
# last-use (clearing after would discard those reads).
|
|
286
|
+
live.clear()
|
|
287
|
+
if stmt.value:
|
|
288
|
+
_process_reads(stmt.value, live, last_uses, source_aliases, detached_aliases)
|
|
289
|
+
|
|
290
|
+
elif isinstance(stmt, TpyRaise):
|
|
291
|
+
# Terminating (transfers to a handler / unwinds). Clear normal-flow
|
|
292
|
+
# live, then process the raised exception's own reads so an earlier
|
|
293
|
+
# consume of a var read here is not misread as last-use.
|
|
294
|
+
live.clear()
|
|
295
|
+
for expr in stmt.exprs():
|
|
296
|
+
_process_reads(expr, live, last_uses, source_aliases, detached_aliases)
|
|
297
|
+
|
|
298
|
+
elif isinstance(stmt, TpyWith):
|
|
299
|
+
_analyze_with(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
300
|
+
|
|
301
|
+
elif isinstance(stmt, TpyTry):
|
|
302
|
+
_analyze_try(stmt, live, last_uses, source_aliases, detached_aliases)
|
|
303
|
+
|
|
304
|
+
elif isinstance(stmt, TpyDelVar):
|
|
305
|
+
for name in stmt.names:
|
|
306
|
+
live.discard(name)
|
|
307
|
+
|
|
308
|
+
elif isinstance(stmt, TpyNestedDef):
|
|
309
|
+
# Captured vars are referenced by the closure (by-ref or by-value).
|
|
310
|
+
# They must stay live so earlier uses aren't incorrectly marked as
|
|
311
|
+
# last-use (which would cause std::move before the capture).
|
|
312
|
+
if stmt.captured_names:
|
|
313
|
+
for name in stmt.captured_names:
|
|
314
|
+
live.add(name)
|
|
315
|
+
live.discard(stmt.func.name)
|
|
316
|
+
|
|
317
|
+
else:
|
|
318
|
+
# Non-terminating, non-defining statements (yield, expr-stmt, assert,
|
|
319
|
+
# del-item, del-attr, ...): process every read-bearing child expression.
|
|
320
|
+
# Routing through exprs() means a future read-bearing statement is
|
|
321
|
+
# covered automatically rather than silently dropping its reads.
|
|
322
|
+
for expr in stmt.exprs():
|
|
323
|
+
_process_reads(expr, live, last_uses, source_aliases, detached_aliases)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _analyze_if(
|
|
327
|
+
stmt: TpyIf,
|
|
328
|
+
live: set[str],
|
|
329
|
+
last_uses: set[int],
|
|
330
|
+
source_aliases: _Aliases,
|
|
331
|
+
detached_aliases: set[str],
|
|
332
|
+
) -> None:
|
|
333
|
+
"""Analyze if/else with branch merging."""
|
|
334
|
+
then_terminates = stmts_terminate(stmt.then_body)
|
|
335
|
+
else_terminates = stmts_terminate(stmt.else_body)
|
|
336
|
+
|
|
337
|
+
# If a branch terminates, post-if code is unreachable on that path --
|
|
338
|
+
# start with empty live set instead of inheriting post-if liveness.
|
|
339
|
+
live_then = set() if then_terminates else live.copy()
|
|
340
|
+
_analyze_stmts_backward(stmt.then_body, live_then, last_uses, source_aliases, detached_aliases)
|
|
341
|
+
|
|
342
|
+
live_else = set() if else_terminates else live.copy()
|
|
343
|
+
_analyze_stmts_backward(stmt.else_body, live_else, last_uses, source_aliases, detached_aliases)
|
|
344
|
+
|
|
345
|
+
# After both branches: union (conservative -- live if used in either path)
|
|
346
|
+
live.clear()
|
|
347
|
+
live.update(live_then)
|
|
348
|
+
live.update(live_else)
|
|
349
|
+
|
|
350
|
+
# Process condition reads (evaluated before either branch)
|
|
351
|
+
_process_reads(stmt.condition, live, last_uses, source_aliases, detached_aliases)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _analyze_match(
|
|
355
|
+
stmt: TpyMatch,
|
|
356
|
+
live: set[str],
|
|
357
|
+
last_uses: set[int],
|
|
358
|
+
source_aliases: _Aliases,
|
|
359
|
+
detached_aliases: set[str],
|
|
360
|
+
) -> None:
|
|
361
|
+
"""Analyze match/case with per-arm branch merging (same as if/else)."""
|
|
362
|
+
# Case patterns are not read-tracked: value patterns (`case X.Y:`) reference
|
|
363
|
+
# module-level constants, never movable locals, so they carry no last-use.
|
|
364
|
+
merged_live: set[str] = set()
|
|
365
|
+
for case in stmt.cases:
|
|
366
|
+
arm_terminates = stmts_terminate(case.body)
|
|
367
|
+
arm_live = set() if arm_terminates else live.copy()
|
|
368
|
+
_analyze_stmts_backward(case.body, arm_live, last_uses, source_aliases, detached_aliases)
|
|
369
|
+
# Guard expression reads
|
|
370
|
+
if case.guard is not None:
|
|
371
|
+
_process_reads(case.guard, arm_live, last_uses, source_aliases, detached_aliases)
|
|
372
|
+
merged_live.update(arm_live)
|
|
373
|
+
|
|
374
|
+
live.clear()
|
|
375
|
+
live.update(merged_live)
|
|
376
|
+
# Subject expression reads
|
|
377
|
+
_process_reads(stmt.subject, live, last_uses, source_aliases, detached_aliases)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _analyze_while(
|
|
381
|
+
stmt: TpyWhile,
|
|
382
|
+
live: set[str],
|
|
383
|
+
last_uses: set[int],
|
|
384
|
+
source_aliases: _Aliases,
|
|
385
|
+
detached_aliases: set[str],
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Analyze while loop with fixpoint iteration."""
|
|
388
|
+
# Analyze else block first (runs after loop, before subsequent code)
|
|
389
|
+
if stmt.orelse:
|
|
390
|
+
_analyze_stmts_backward(stmt.orelse, live, last_uses, source_aliases, detached_aliases)
|
|
391
|
+
|
|
392
|
+
# Fixpoint: variables used in the loop body are live across iterations.
|
|
393
|
+
# Iterate until the live set stabilizes.
|
|
394
|
+
post_loop_live = live.copy()
|
|
395
|
+
|
|
396
|
+
for _ in range(4):
|
|
397
|
+
prev_live = live.copy()
|
|
398
|
+
|
|
399
|
+
# Walk body backward to compute live set (marking disabled)
|
|
400
|
+
body_live = live.copy()
|
|
401
|
+
_compute_live_only(stmt.body, body_live)
|
|
402
|
+
|
|
403
|
+
# Condition reads contribute to liveness
|
|
404
|
+
for node in _collect_reads_expr(stmt.condition):
|
|
405
|
+
body_live.add(node.name)
|
|
406
|
+
|
|
407
|
+
# Loop-back: names live at body start are also live at body end
|
|
408
|
+
live.clear()
|
|
409
|
+
live.update(body_live | post_loop_live)
|
|
410
|
+
|
|
411
|
+
if live == prev_live:
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
# Final marking pass with stabilized live set
|
|
415
|
+
_analyze_stmts_backward(stmt.body, live, last_uses, source_aliases, detached_aliases)
|
|
416
|
+
|
|
417
|
+
# Process condition reads
|
|
418
|
+
_process_reads(stmt.condition, live, last_uses, source_aliases, detached_aliases)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _analyze_for_each(
|
|
422
|
+
stmt: TpyForEach,
|
|
423
|
+
live: set[str],
|
|
424
|
+
last_uses: set[int],
|
|
425
|
+
source_aliases: _Aliases,
|
|
426
|
+
detached_aliases: set[str],
|
|
427
|
+
) -> None:
|
|
428
|
+
"""Analyze for-each loop with fixpoint iteration."""
|
|
429
|
+
# Analyze else block first (runs after loop, before subsequent code)
|
|
430
|
+
if stmt.orelse:
|
|
431
|
+
_analyze_stmts_backward(stmt.orelse, live, last_uses, source_aliases, detached_aliases)
|
|
432
|
+
|
|
433
|
+
post_loop_live = live.copy()
|
|
434
|
+
|
|
435
|
+
for _ in range(4):
|
|
436
|
+
prev_live = live.copy()
|
|
437
|
+
|
|
438
|
+
# Walk body backward to compute live set
|
|
439
|
+
body_live = live.copy()
|
|
440
|
+
_compute_live_only(stmt.body, body_live)
|
|
441
|
+
|
|
442
|
+
# Loop variable is killed at loop header (reassigned each iteration)
|
|
443
|
+
body_live.discard(stmt.var)
|
|
444
|
+
|
|
445
|
+
# Loop-back: names live at body start are also live at body end
|
|
446
|
+
live.clear()
|
|
447
|
+
live.update(body_live | post_loop_live)
|
|
448
|
+
|
|
449
|
+
if live == prev_live:
|
|
450
|
+
break
|
|
451
|
+
|
|
452
|
+
# Final marking pass with stabilized live set
|
|
453
|
+
_analyze_stmts_backward(stmt.body, live, last_uses, source_aliases, detached_aliases)
|
|
454
|
+
|
|
455
|
+
# Kill the loop variable (assigned by the loop at each iteration)
|
|
456
|
+
live.discard(stmt.var)
|
|
457
|
+
|
|
458
|
+
# Process iterable reads
|
|
459
|
+
_process_reads(stmt.iterable, live, last_uses, source_aliases, detached_aliases)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _all_read_names(stmts: list[TpyStmt]) -> list[TpyName]:
|
|
463
|
+
"""May over-collect defined names (e.g. a plain assign target renders as a
|
|
464
|
+
TpyName); callers only consult node identity against `last_uses`, where
|
|
465
|
+
those surplus nodes never appear, so the surplus is harmless.
|
|
466
|
+
"""
|
|
467
|
+
result: list[TpyName] = []
|
|
468
|
+
for stmt in stmts:
|
|
469
|
+
for expr in stmt.exprs():
|
|
470
|
+
result.extend(_collect_reads_expr(expr))
|
|
471
|
+
for body in stmt.sub_bodies():
|
|
472
|
+
result.extend(_all_read_names(body))
|
|
473
|
+
return result
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def _analyze_with(
|
|
477
|
+
stmt: TpyWith,
|
|
478
|
+
live: set[str],
|
|
479
|
+
last_uses: set[int],
|
|
480
|
+
source_aliases: _Aliases,
|
|
481
|
+
detached_aliases: set[str],
|
|
482
|
+
) -> None:
|
|
483
|
+
"""Analyze a with statement. `__exit__` runs after the body on every path
|
|
484
|
+
but reads only the context manager, not body locals, so the body is a
|
|
485
|
+
plain sequential block: recurse it, kill the `as` targets (bound at
|
|
486
|
+
entry), then process the context-manager expressions (evaluated at entry).
|
|
487
|
+
"""
|
|
488
|
+
_analyze_stmts_backward(stmt.body, live, last_uses, source_aliases, detached_aliases)
|
|
489
|
+
for item in reversed(stmt.items):
|
|
490
|
+
if item.target is not None:
|
|
491
|
+
live.discard(item.target)
|
|
492
|
+
_process_reads(item.context_expr, live, last_uses, source_aliases, detached_aliases)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _analyze_try(
|
|
496
|
+
stmt: TpyTry,
|
|
497
|
+
live: set[str],
|
|
498
|
+
last_uses: set[int],
|
|
499
|
+
source_aliases: _Aliases,
|
|
500
|
+
detached_aliases: set[str],
|
|
501
|
+
) -> None:
|
|
502
|
+
"""Analyze a try statement.
|
|
503
|
+
|
|
504
|
+
finally runs last on every path; handlers run on the exception path; else
|
|
505
|
+
runs on the normal path after the try body. An exception can transfer to a
|
|
506
|
+
handler at ANY point in the try body, so a name read by a handler or by
|
|
507
|
+
finally is live across the whole try body and must never be marked
|
|
508
|
+
last-use there -- otherwise a consume in the try body could move a value
|
|
509
|
+
the exception path still reads. We compute the handler/finally/else live
|
|
510
|
+
sets, mark the try body normally, then drop the last-use marks the body
|
|
511
|
+
walk gave to any name read on an exception path.
|
|
512
|
+
"""
|
|
513
|
+
finally_live = live.copy()
|
|
514
|
+
if stmt.finally_body:
|
|
515
|
+
_analyze_stmts_backward(stmt.finally_body, finally_live, last_uses, source_aliases, detached_aliases)
|
|
516
|
+
|
|
517
|
+
handler_union: set[str] = set()
|
|
518
|
+
for h in stmt.handlers:
|
|
519
|
+
h_live = finally_live.copy()
|
|
520
|
+
_analyze_stmts_backward(h.body, h_live, last_uses, source_aliases, detached_aliases)
|
|
521
|
+
if h.binding is not None:
|
|
522
|
+
h_live.discard(h.binding)
|
|
523
|
+
handler_union |= h_live
|
|
524
|
+
|
|
525
|
+
else_live = finally_live.copy()
|
|
526
|
+
if stmt.else_body:
|
|
527
|
+
_analyze_stmts_backward(stmt.else_body, else_live, last_uses, source_aliases, detached_aliases)
|
|
528
|
+
|
|
529
|
+
# Try body: normal exit flows to else, exception at any point to a handler.
|
|
530
|
+
live.clear()
|
|
531
|
+
live.update(else_live | handler_union)
|
|
532
|
+
_analyze_stmts_backward(stmt.try_body, live, last_uses, source_aliases, detached_aliases)
|
|
533
|
+
|
|
534
|
+
exception_path_live = handler_union | finally_live
|
|
535
|
+
if exception_path_live:
|
|
536
|
+
for node in _all_read_names(stmt.try_body):
|
|
537
|
+
if node.name in exception_path_live:
|
|
538
|
+
last_uses.discard(id(node))
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _compute_live_only(stmts: list[TpyStmt], live: set[str]) -> None:
|
|
542
|
+
"""Walk statements backward, updating live set WITHOUT marking last uses.
|
|
543
|
+
|
|
544
|
+
Used during fixpoint iteration to stabilize the live set before
|
|
545
|
+
doing the actual marking pass.
|
|
546
|
+
"""
|
|
547
|
+
for stmt in reversed(stmts):
|
|
548
|
+
_compute_stmt_live_only(stmt, live)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _compute_stmt_live_only(stmt: TpyStmt, live: set[str]) -> None:
|
|
552
|
+
"""Update live set for a statement without marking last uses."""
|
|
553
|
+
|
|
554
|
+
if isinstance(stmt, TpyIf):
|
|
555
|
+
then_terminates = stmts_terminate(stmt.then_body)
|
|
556
|
+
else_terminates = stmts_terminate(stmt.else_body)
|
|
557
|
+
live_then = set() if then_terminates else live.copy()
|
|
558
|
+
_compute_live_only(stmt.then_body, live_then)
|
|
559
|
+
live_else = set() if else_terminates else live.copy()
|
|
560
|
+
_compute_live_only(stmt.else_body, live_else)
|
|
561
|
+
live.clear()
|
|
562
|
+
live.update(live_then | live_else)
|
|
563
|
+
for node in _collect_reads_expr(stmt.condition):
|
|
564
|
+
live.add(node.name)
|
|
565
|
+
|
|
566
|
+
elif isinstance(stmt, TpyMatch):
|
|
567
|
+
merged: set[str] = set()
|
|
568
|
+
for case in stmt.cases:
|
|
569
|
+
arm_terminates = stmts_terminate(case.body)
|
|
570
|
+
arm_live = set() if arm_terminates else live.copy()
|
|
571
|
+
_compute_live_only(case.body, arm_live)
|
|
572
|
+
if case.guard is not None:
|
|
573
|
+
for node in _collect_reads_expr(case.guard):
|
|
574
|
+
arm_live.add(node.name)
|
|
575
|
+
merged.update(arm_live)
|
|
576
|
+
live.clear()
|
|
577
|
+
live.update(merged)
|
|
578
|
+
for node in _collect_reads_expr(stmt.subject):
|
|
579
|
+
live.add(node.name)
|
|
580
|
+
|
|
581
|
+
elif isinstance(stmt, TpyWhile):
|
|
582
|
+
# Approximate: collect all reads in body + condition + orelse
|
|
583
|
+
if stmt.orelse:
|
|
584
|
+
_compute_live_only(stmt.orelse, live)
|
|
585
|
+
for node in _collect_reads_expr(stmt.condition):
|
|
586
|
+
live.add(node.name)
|
|
587
|
+
body_live = live.copy()
|
|
588
|
+
_compute_live_only(stmt.body, body_live)
|
|
589
|
+
live.update(body_live)
|
|
590
|
+
|
|
591
|
+
elif isinstance(stmt, TpyForEach):
|
|
592
|
+
if stmt.orelse:
|
|
593
|
+
_compute_live_only(stmt.orelse, live)
|
|
594
|
+
for node in _collect_reads_expr(stmt.iterable):
|
|
595
|
+
live.add(node.name)
|
|
596
|
+
body_live = live.copy()
|
|
597
|
+
_compute_live_only(stmt.body, body_live)
|
|
598
|
+
body_live.discard(stmt.var)
|
|
599
|
+
live.update(body_live)
|
|
600
|
+
|
|
601
|
+
elif isinstance(stmt, TpyVarDecl):
|
|
602
|
+
# No hide_alias here (unlike _analyze_stmt) -- conservative for loop
|
|
603
|
+
# fixpoint: prevents move-through inside loop bodies where the source
|
|
604
|
+
# may be live from prior iterations.
|
|
605
|
+
if stmt.init:
|
|
606
|
+
for node in _collect_reads_expr(stmt.init):
|
|
607
|
+
live.add(node.name)
|
|
608
|
+
live.discard(stmt.name)
|
|
609
|
+
|
|
610
|
+
elif isinstance(stmt, TpyTupleUnpack):
|
|
611
|
+
for node in _collect_reads_expr(stmt.value):
|
|
612
|
+
live.add(node.name)
|
|
613
|
+
for name in stmt.targets:
|
|
614
|
+
if name is not None:
|
|
615
|
+
live.discard(name)
|
|
616
|
+
|
|
617
|
+
elif isinstance(stmt, TpyAssign):
|
|
618
|
+
for node in _collect_reads_expr(stmt.value):
|
|
619
|
+
live.add(node.name)
|
|
620
|
+
if isinstance(stmt.target, TpySubscript):
|
|
621
|
+
for node in _collect_reads_expr(stmt.target.obj):
|
|
622
|
+
live.add(node.name)
|
|
623
|
+
for node in _collect_reads_expr(stmt.target.index):
|
|
624
|
+
live.add(node.name)
|
|
625
|
+
elif isinstance(stmt.target, TpyFieldAccess):
|
|
626
|
+
for node in _collect_reads_expr(stmt.target.obj):
|
|
627
|
+
live.add(node.name)
|
|
628
|
+
elif isinstance(stmt.target, TpyName):
|
|
629
|
+
live.discard(stmt.target.name)
|
|
630
|
+
|
|
631
|
+
elif isinstance(stmt, TpyAugAssign):
|
|
632
|
+
for node in _collect_reads_expr(stmt.value):
|
|
633
|
+
live.add(node.name)
|
|
634
|
+
if isinstance(stmt.target, TpySubscript):
|
|
635
|
+
for node in _collect_reads_expr(stmt.target.obj):
|
|
636
|
+
live.add(node.name)
|
|
637
|
+
for node in _collect_reads_expr(stmt.target.index):
|
|
638
|
+
live.add(node.name)
|
|
639
|
+
elif isinstance(stmt.target, TpyFieldAccess):
|
|
640
|
+
for node in _collect_reads_expr(stmt.target.obj):
|
|
641
|
+
live.add(node.name)
|
|
642
|
+
elif isinstance(stmt.target, TpyName):
|
|
643
|
+
live.add(stmt.target.name)
|
|
644
|
+
|
|
645
|
+
elif isinstance(stmt, TpyReturn):
|
|
646
|
+
# Clear before adding reads: the return terminates this path, so only
|
|
647
|
+
# the return-expression reads are live-before (clearing after would
|
|
648
|
+
# discard them).
|
|
649
|
+
live.clear()
|
|
650
|
+
if stmt.value:
|
|
651
|
+
for node in _collect_reads_expr(stmt.value):
|
|
652
|
+
live.add(node.name)
|
|
653
|
+
|
|
654
|
+
elif isinstance(stmt, TpyRaise):
|
|
655
|
+
live.clear()
|
|
656
|
+
for expr in stmt.exprs():
|
|
657
|
+
for node in _collect_reads_expr(expr):
|
|
658
|
+
live.add(node.name)
|
|
659
|
+
|
|
660
|
+
elif isinstance(stmt, TpyWith):
|
|
661
|
+
_compute_live_only(stmt.body, live)
|
|
662
|
+
for item in reversed(stmt.items):
|
|
663
|
+
if item.target is not None:
|
|
664
|
+
live.discard(item.target)
|
|
665
|
+
for node in _collect_reads_expr(item.context_expr):
|
|
666
|
+
live.add(node.name)
|
|
667
|
+
|
|
668
|
+
elif isinstance(stmt, TpyTry):
|
|
669
|
+
# Conservative over-approximation: any sub-body's reads may be live
|
|
670
|
+
# entering the try (an exception can transfer mid-body to a handler).
|
|
671
|
+
if stmt.finally_body:
|
|
672
|
+
_compute_live_only(stmt.finally_body, live)
|
|
673
|
+
for h in stmt.handlers:
|
|
674
|
+
h_live = live.copy()
|
|
675
|
+
_compute_live_only(h.body, h_live)
|
|
676
|
+
if h.binding is not None:
|
|
677
|
+
h_live.discard(h.binding)
|
|
678
|
+
live.update(h_live)
|
|
679
|
+
if stmt.else_body:
|
|
680
|
+
_compute_live_only(stmt.else_body, live)
|
|
681
|
+
_compute_live_only(stmt.try_body, live)
|
|
682
|
+
|
|
683
|
+
elif isinstance(stmt, TpyDelVar):
|
|
684
|
+
for name in stmt.names:
|
|
685
|
+
live.discard(name)
|
|
686
|
+
|
|
687
|
+
elif isinstance(stmt, TpyNestedDef):
|
|
688
|
+
if stmt.captured_names:
|
|
689
|
+
for name in stmt.captured_names:
|
|
690
|
+
live.add(name)
|
|
691
|
+
live.discard(stmt.func.name)
|
|
692
|
+
|
|
693
|
+
else:
|
|
694
|
+
# yield, expr-stmt, assert, del-item, del-attr, ...: add child-expr reads.
|
|
695
|
+
for expr in stmt.exprs():
|
|
696
|
+
for node in _collect_reads_expr(expr):
|
|
697
|
+
live.add(node.name)
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
# -- Read collection ----------------------------------------------------------
|
|
701
|
+
|
|
702
|
+
def _process_reads(
|
|
703
|
+
expr: TpyExpr,
|
|
704
|
+
live: set[str],
|
|
705
|
+
last_uses: set[int],
|
|
706
|
+
source_aliases: _Aliases,
|
|
707
|
+
detached_aliases: set[str],
|
|
708
|
+
) -> None:
|
|
709
|
+
"""Collect name reads in an expression, mark last uses, update live set."""
|
|
710
|
+
walrus_defs: set[str] = set()
|
|
711
|
+
reads = _collect_reads_expr(expr, walrus_defs)
|
|
712
|
+
if not reads and not walrus_defs:
|
|
713
|
+
return
|
|
714
|
+
|
|
715
|
+
# Count occurrences per variable name in this expression.
|
|
716
|
+
# If a variable appears multiple times, don't mark any as last use
|
|
717
|
+
# (C++ argument evaluation order is unspecified).
|
|
718
|
+
name_counts: dict[str, int] = {}
|
|
719
|
+
for node in reads:
|
|
720
|
+
name_counts[node.name] = name_counts.get(node.name, 0) + 1
|
|
721
|
+
|
|
722
|
+
# Mark single-occurrence reads that are not live (not used later)
|
|
723
|
+
# and have no live T& aliases (prevents dangling references)
|
|
724
|
+
for node in reads:
|
|
725
|
+
if (name_counts[node.name] == 1
|
|
726
|
+
and node.name not in live
|
|
727
|
+
and not _has_live_alias(node.name, live, source_aliases, detached_aliases)):
|
|
728
|
+
last_uses.add(id(node))
|
|
729
|
+
|
|
730
|
+
# Add all read names to live set
|
|
731
|
+
for node in reads:
|
|
732
|
+
live.add(node.name)
|
|
733
|
+
|
|
734
|
+
# Walrus targets are definitions -- kill them from live set
|
|
735
|
+
live -= walrus_defs
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def _collect_reads_expr(expr: TpyExpr,
|
|
739
|
+
walrus_defs: set[str] | None = None) -> list[TpyName]:
|
|
740
|
+
"""Collect TpyName nodes read (not defined) in an expression.
|
|
741
|
+
|
|
742
|
+
Uses TpyExpr.children() for iterative traversal.
|
|
743
|
+
If walrus_defs is provided, also records walrus (:=) target names into it
|
|
744
|
+
(those are definitions, not reads, and are excluded from the returned list).
|
|
745
|
+
"""
|
|
746
|
+
result: list[TpyName] = []
|
|
747
|
+
stack: list[TpyExpr] = [expr]
|
|
748
|
+
while stack:
|
|
749
|
+
node = stack.pop()
|
|
750
|
+
if isinstance(node, TpyName):
|
|
751
|
+
result.append(node)
|
|
752
|
+
continue
|
|
753
|
+
if isinstance(node, TpyNamedExpr) and walrus_defs is not None:
|
|
754
|
+
walrus_defs.add(node.target)
|
|
755
|
+
stack.extend(node.children())
|
|
756
|
+
return result
|