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
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
"""Control-flow graph for resumable-frame lowering (async def today; the
|
|
2
|
+
future generator migration consumes the same module).
|
|
3
|
+
|
|
4
|
+
A resumable frame is a state-machine struct whose body method (`__poll__`
|
|
5
|
+
for async, `__next__` for generators) dispatches on a state integer to
|
|
6
|
+
the right resume point. To support `await` (or `yield`) inside arbitrary
|
|
7
|
+
control flow -- if/while/for/try/with -- we lower the function body to
|
|
8
|
+
a CFG of basic blocks, then emit each suspension's resume case body
|
|
9
|
+
wrapped in the source-level try/except/finally/with stack that was
|
|
10
|
+
active at that suspension point. C++'s rule that case labels inside try
|
|
11
|
+
blocks aren't reachable from outside the try forces this "replay the
|
|
12
|
+
region stack inside each case" pattern; it's what Roslyn's async
|
|
13
|
+
rewriter does for the same reason.
|
|
14
|
+
|
|
15
|
+
This module owns:
|
|
16
|
+
- Basic-block + terminator + region data types.
|
|
17
|
+
- The CFG builder that walks a resumable (async def / generator) body.
|
|
18
|
+
- (Future task) Conservative cross-suspension liveness.
|
|
19
|
+
- (Future task) State assignment.
|
|
20
|
+
|
|
21
|
+
Emission is in `gen_async.py` (and, later, `gen_generators.py`); the
|
|
22
|
+
CFG itself is shape-neutral.
|
|
23
|
+
|
|
24
|
+
Generator migration plan: SuspensionPayload is a tagged union with two
|
|
25
|
+
variants -- AwaitPayload (async) and YieldPayload (generator). The
|
|
26
|
+
builder dispatches on the node it encounters: a top-level `await` shape
|
|
27
|
+
produces an AwaitPayload, a `yield` statement produces a YieldPayload,
|
|
28
|
+
and the decomposition predicate (`_stmt_has_any_suspension`) treats both
|
|
29
|
+
uniformly. The generator emitter that consumes YieldPayload is the next
|
|
30
|
+
migration step.
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from dataclasses import dataclass, field
|
|
35
|
+
from enum import Enum
|
|
36
|
+
from typing import TYPE_CHECKING, Union
|
|
37
|
+
|
|
38
|
+
from ..parse.nodes import (
|
|
39
|
+
TpyAssign, TpyAwait, TpyBreak, TpyContinue, TpyExceptHandler,
|
|
40
|
+
TpyExpr, TpyExprStmt, TpyForEach, TpyIf, TpyName,
|
|
41
|
+
TpyMatch,
|
|
42
|
+
TpyRaise, TpyReturn, TpyStmt, TpyTry, TpyVarDecl, TpyWhile, TpyWith,
|
|
43
|
+
TpyWithItem, TpyYield,
|
|
44
|
+
stmt_has_any_suspension as _stmt_has_any_suspension,
|
|
45
|
+
stmts_have_any_suspension as _stmts_have_any_suspension,
|
|
46
|
+
stmts_have_any_return as _stmts_have_any_return,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from ..parse.nodes import TpyFunction
|
|
51
|
+
from .gen_generators import GeneratorForInfo
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class ResumableFuncState:
|
|
56
|
+
"""Per-function codegen state for the resumable-frame lowering, shared by
|
|
57
|
+
generators and `async def` coroutines. Attached to a `TpyFunction` as
|
|
58
|
+
`func._resumable_state` (via `resumable_state()`), built lazily by the
|
|
59
|
+
prescan / CFG-build / eligibility passes and consumed by the struct +
|
|
60
|
+
body emit.
|
|
61
|
+
|
|
62
|
+
Replaces ~16 individually string-keyed `getattr(func, "_resumable_*" /
|
|
63
|
+
"_async_*" / "_with_*")` side tables with one typed attribute: no
|
|
64
|
+
`getattr(..., None) or {}` ritual, no silent-None-on-typo miscompile, and
|
|
65
|
+
the prescan->emit contract (pass P populates fields A1..An) is visible in
|
|
66
|
+
one place. The `_async_*`-named members keep that prefix because they are
|
|
67
|
+
genuinely async-only (sub-coro struct names from `async for`/`async
|
|
68
|
+
with`, await-arg lifting); the rest are shape-neutral."""
|
|
69
|
+
# CFG build (_build_resumable_cfg)
|
|
70
|
+
cfg: 'CFG | None' = None
|
|
71
|
+
cfg_builder: 'CFGBuilder | None' = None
|
|
72
|
+
# await-arg lifting (_effective_body / _lift_nested_awaits)
|
|
73
|
+
lifted_body: 'list[TpyStmt] | None' = None
|
|
74
|
+
next_lift_id: int = 0
|
|
75
|
+
# for-loop prescan (_prescan_resumable_for_loops). `*_prescanned` flags
|
|
76
|
+
# preserve the original "cache even when the result is empty" semantics
|
|
77
|
+
# (an empty map distinct from "not yet prescanned").
|
|
78
|
+
for_prescanned: bool = False
|
|
79
|
+
for_uid_map: 'dict[int, int]' = field(default_factory=dict)
|
|
80
|
+
for_fields: 'list[tuple[str, str]]' = field(default_factory=list)
|
|
81
|
+
for_loop_info: 'dict[int, GeneratorForInfo]' = field(default_factory=dict)
|
|
82
|
+
for_info_by_uid: 'dict[int, GeneratorForInfo]' = field(default_factory=dict)
|
|
83
|
+
async_for_struct_names: 'dict[int, str]' = field(default_factory=dict)
|
|
84
|
+
# with-stmt prescan (_prescan_with_stmts)
|
|
85
|
+
with_prescanned: bool = False
|
|
86
|
+
with_uid_map: 'dict[int, list[int]]' = field(default_factory=dict)
|
|
87
|
+
with_fields: 'list[tuple[str, str]]' = field(default_factory=list)
|
|
88
|
+
with_owning_str_targets: 'set[str]' = field(default_factory=set)
|
|
89
|
+
async_with_struct_names: 'dict[int, tuple[str, str]]' = field(default_factory=dict)
|
|
90
|
+
# try/finally prescan (_prescan_resumable_try_finally)
|
|
91
|
+
try_finally_prescanned: bool = False
|
|
92
|
+
try_finally_uid_map: 'dict[int, int]' = field(default_factory=dict)
|
|
93
|
+
try_finally_fields: 'list[tuple[str, str]]' = field(default_factory=list)
|
|
94
|
+
# resumable-generator eligibility memoization (generator.py gate)
|
|
95
|
+
gen_eligible: 'bool | None' = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def resumable_state(func: 'TpyFunction') -> ResumableFuncState:
|
|
99
|
+
"""Get-or-create the `ResumableFuncState` attached to `func`."""
|
|
100
|
+
state = getattr(func, "_resumable_state", None)
|
|
101
|
+
if state is None:
|
|
102
|
+
state = ResumableFuncState()
|
|
103
|
+
func._resumable_state = state
|
|
104
|
+
return state
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# -- Resumable-frame shape: which state machine the emitter produces.
|
|
108
|
+
|
|
109
|
+
class ResumableShape(Enum):
|
|
110
|
+
"""Which resumable-frame shape the emitter (`gen_async.py`) produces.
|
|
111
|
+
|
|
112
|
+
ASYNC -- `async def`: `await` -> `__poll__(Waker) -> Poll<T>`.
|
|
113
|
+
GENERATOR -- generator: `yield` -> `__next__() -> expected<T,
|
|
114
|
+
StopIteration>`.
|
|
115
|
+
"""
|
|
116
|
+
ASYNC = "async"
|
|
117
|
+
GENERATOR = "generator"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# -- AwaitPayload-specific enums (kept here so resumable_cfg owns the
|
|
121
|
+
# CFG-level metadata; gen_async re-imports them).
|
|
122
|
+
|
|
123
|
+
class AwaitMode(Enum):
|
|
124
|
+
"""How an await transition stores its sub-future in the parent frame.
|
|
125
|
+
|
|
126
|
+
INLINE -- statically-known async def; sub-coro struct emplaced as a
|
|
127
|
+
sub-future field.
|
|
128
|
+
ERASED -- value awaitable (Task[T] / user value type implementing
|
|
129
|
+
Awaitable[T]); move-stored in an std::optional sub-future.
|
|
130
|
+
BORROWED -- reference-type awaitable (e.g. Future[T]); stored as a
|
|
131
|
+
raw pointer so observers see the same object across suspensions.
|
|
132
|
+
"""
|
|
133
|
+
INLINE = "inline"
|
|
134
|
+
ERASED = "erased"
|
|
135
|
+
BORROWED = "borrowed"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class AwaitKind(Enum):
|
|
139
|
+
"""How an await expression's result is consumed at its host stmt."""
|
|
140
|
+
ASSIGN = "assign"
|
|
141
|
+
VARDECL = "vardecl"
|
|
142
|
+
RETURN = "return"
|
|
143
|
+
DISCARD = "discard"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class AsyncWithKind(Enum):
|
|
147
|
+
"""Which leg of an `async with` a synthetic yield emits.
|
|
148
|
+
|
|
149
|
+
M5's CFG synthesizes two Yield BBs per async-with: one for
|
|
150
|
+
`await __cm.__aenter__()` and one for `await __cm.__aexit__(...)`.
|
|
151
|
+
Emit dispatches on this enum rather than re-parsing the AST.
|
|
152
|
+
"""
|
|
153
|
+
AENTER = "aenter"
|
|
154
|
+
AEXIT = "aexit"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# -- Suspension payloads (the shape-specific part of a yield terminator)
|
|
158
|
+
|
|
159
|
+
@dataclass(frozen=True)
|
|
160
|
+
class AwaitPayload:
|
|
161
|
+
"""An async-def suspension. Held inside a Yield terminator."""
|
|
162
|
+
mode: AwaitMode
|
|
163
|
+
sub_field_cpp_type: str # cpp type for std::optional<...> / pointee
|
|
164
|
+
operand_expr: TpyExpr # the operand of `await ...`
|
|
165
|
+
kind: AwaitKind
|
|
166
|
+
bind_target: str | None # variable name for ASSIGN / VARDECL
|
|
167
|
+
return_stmt: TpyReturn | None # the original return for RETURN-kind
|
|
168
|
+
host_stmt: TpyStmt | None # source-level statement containing the await
|
|
169
|
+
# The TpyAwait node itself (sema attaches awaited_async_func_name /
|
|
170
|
+
# awaited_task_inner here; emit consults them).
|
|
171
|
+
await_node: TpyAwait
|
|
172
|
+
# Async-with internal yields: emit takes a special path that
|
|
173
|
+
# synthesizes `(*__with_ctx_<n>).__aenter__()` or
|
|
174
|
+
# `(*__with_ctx_<n>).__aexit__({}, nullptr, {})` directly rather
|
|
175
|
+
# than going through gen_expr on a synthesized AST. Set by
|
|
176
|
+
# `_build_async_with`; None for ordinary user awaits.
|
|
177
|
+
async_with_kind: 'AsyncWithKind | None' = None
|
|
178
|
+
async_with_ctx_n: int | None = None
|
|
179
|
+
# Set by `_build_async_for` so emit can synthesize
|
|
180
|
+
# `__sub_<i>.emplace(*__for_itr_<uid>)` and look up the sub-coro
|
|
181
|
+
# struct name via `func._async_for_struct_names`. None for
|
|
182
|
+
# ordinary user awaits.
|
|
183
|
+
async_for_uid: int | None = None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@dataclass(frozen=True)
|
|
187
|
+
class YieldPayload:
|
|
188
|
+
"""A generator suspension. Held inside a Yield terminator when the CFG
|
|
189
|
+
is built from a generator body."""
|
|
190
|
+
value_expr: TpyExpr | None # the yielded expression (None for bare yield)
|
|
191
|
+
# The source `yield` statement, so emit can reuse the statement
|
|
192
|
+
# generator's `gen_yield_value` (storage->borrow bridging etc.).
|
|
193
|
+
yield_stmt: 'TpyYield | None' = None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
SuspensionPayload = Union[AwaitPayload, YieldPayload]
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# -- Regions: try/finally/with frames active at a BB
|
|
200
|
+
|
|
201
|
+
@dataclass(frozen=True)
|
|
202
|
+
class TryRegion:
|
|
203
|
+
"""A try/except/finally frame active inside the try body.
|
|
204
|
+
|
|
205
|
+
`handlers` are the source-level except handlers; emit wraps the
|
|
206
|
+
case body in `catch (Type& e) { handler.body }` per handler.
|
|
207
|
+
|
|
208
|
+
Two finally shapes:
|
|
209
|
+
* Helper-based (`finally_helper_name` set): finally body has no
|
|
210
|
+
awaits and is emitted as a member function `void __finally_<n>()`
|
|
211
|
+
called on every throw / return / fall-through exit.
|
|
212
|
+
* CFG-based (`finally_entry_bb` / `captured_exc_field` set):
|
|
213
|
+
finally body has an await and lives in the main state machine.
|
|
214
|
+
On exception in the try body, the catch-all saves
|
|
215
|
+
`std::current_exception()` to the captured-exc frame field and
|
|
216
|
+
transitions state to `finally_entry_bb`. The finally body ends
|
|
217
|
+
with an `AsyncFinallyExit` synthetic stmt that rethrows the
|
|
218
|
+
saved exception (clearing the field) before falling through to
|
|
219
|
+
the post-try BB. Mutually exclusive with the helper-based shape.
|
|
220
|
+
"""
|
|
221
|
+
handlers: tuple[TpyExceptHandler, ...]
|
|
222
|
+
finally_helper_name: str | None # None if try has no finally body OR CFG-based
|
|
223
|
+
tier: str # "throw" / "return" / "finally_only"
|
|
224
|
+
# Source-level loc for emit diagnostics.
|
|
225
|
+
loc_source: TpyStmt | None = None
|
|
226
|
+
# CFG-based finally fields (None for helper-based / no finally).
|
|
227
|
+
finally_entry_bb: int | None = None
|
|
228
|
+
captured_exc_field: str | None = None
|
|
229
|
+
# Pending-return slot for CFG-based finally: when set, a `return`
|
|
230
|
+
# inside the try body or any handler stores its value to
|
|
231
|
+
# `pending_return_slot` (when non-None -- void async defs leave it
|
|
232
|
+
# None) and sets `pending_return_flag = true`, then transitions
|
|
233
|
+
# state to the finally entry BB. AsyncFinallyExit checks the flag
|
|
234
|
+
# and emits the deferred Poll::ready after the rethrow check.
|
|
235
|
+
pending_return_flag: str | None = None
|
|
236
|
+
pending_return_slot: str | None = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dataclass(frozen=True)
|
|
240
|
+
class FinallyRegion:
|
|
241
|
+
"""Marker for BBs inside a finally body. Emit treats specially:
|
|
242
|
+
a throw inside a finally re-raises *after* the body completes;
|
|
243
|
+
a `return` inside a finally body parks into the same pending-return
|
|
244
|
+
slot as a return from the try / handler body (`finally_exit_bb`
|
|
245
|
+
drains it via AsyncFinallyExit, Python: return-in-finally wins and
|
|
246
|
+
swallows any in-flight exception).
|
|
247
|
+
"""
|
|
248
|
+
helper_name: str
|
|
249
|
+
loc_source: TpyStmt | None = None
|
|
250
|
+
# Parking fields mirror TryRegion's so a `return` inside the finally
|
|
251
|
+
# body sees the same slot as a return inside the try body. None when
|
|
252
|
+
# the try has no reachable returns anywhere in try / handlers /
|
|
253
|
+
# finally (no slot allocated).
|
|
254
|
+
pending_return_flag: str | None = None
|
|
255
|
+
pending_return_slot: str | None = None
|
|
256
|
+
# Dedicated BB whose single stmt is AsyncFinallyExit. Both normal
|
|
257
|
+
# fall-through and a return-in-finally jump here, so AsyncFinallyExit
|
|
258
|
+
# is the single replay site.
|
|
259
|
+
finally_exit_bb: int | None = None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@dataclass(frozen=True)
|
|
263
|
+
class ExceptRegion:
|
|
264
|
+
"""Marker for BBs inside an except handler body. The corresponding
|
|
265
|
+
try frame is no longer on the stack (we've already caught the
|
|
266
|
+
exception). The sub-future reset happens in the C++ catch wrapper
|
|
267
|
+
emitted around the resume case body whose suspension threw -- so
|
|
268
|
+
the reset is per-yield, not per-handler.
|
|
269
|
+
|
|
270
|
+
`parent_finally` mirrors the enclosing TryRegion's
|
|
271
|
+
`finally_helper_name` so emit can run the finally body when control
|
|
272
|
+
leaves the handler normally (Python semantics: finally runs after
|
|
273
|
+
a matched except).
|
|
274
|
+
|
|
275
|
+
`parent_finally_entry_bb` is set instead when the parent try has
|
|
276
|
+
a CFG-based finally: an exception raised inside the handler body
|
|
277
|
+
must transition state to that BB (the inner try/catch in the
|
|
278
|
+
handler emit dispatches this via the TryRegion's
|
|
279
|
+
`captured_exc_field`). A `return` inside the handler body routes
|
|
280
|
+
through the same pending-return slot as a return in the try body.
|
|
281
|
+
"""
|
|
282
|
+
handler: TpyExceptHandler
|
|
283
|
+
parent_finally: str | None = None
|
|
284
|
+
loc_source: TpyStmt | None = None
|
|
285
|
+
parent_finally_entry_bb: int | None = None
|
|
286
|
+
parent_pending_return_flag: str | None = None
|
|
287
|
+
parent_pending_return_slot: str | None = None
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@dataclass(frozen=True)
|
|
291
|
+
class WithRegion:
|
|
292
|
+
"""A with frame active inside the body. Emit wraps the case body in
|
|
293
|
+
a try/catch that runs __exit__ with the exception args and either
|
|
294
|
+
suppresses or re-raises depending on __exit__'s return.
|
|
295
|
+
|
|
296
|
+
`ctx_n` is the uid used to name both the `__with_ctx_<n>` frame
|
|
297
|
+
slot (where the context-manager object lives, as
|
|
298
|
+
`std::optional<T>`) and the `__exc_<n>` BaseException& binding
|
|
299
|
+
inside the catch wrap. `post_with_bb` is the CFG BB to transition
|
|
300
|
+
to when __exit__ suppresses an exception (only meaningful when
|
|
301
|
+
item.exit_can_suppress).
|
|
302
|
+
"""
|
|
303
|
+
item: TpyWithItem
|
|
304
|
+
ctx_n: int
|
|
305
|
+
post_with_bb: int
|
|
306
|
+
loc_source: TpyStmt | None = None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Region = Union[TryRegion, FinallyRegion, ExceptRegion, WithRegion]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# -- Terminators: one per BB
|
|
313
|
+
|
|
314
|
+
@dataclass(frozen=True)
|
|
315
|
+
class Fall:
|
|
316
|
+
"""Fall through to next_bb. Used for straight-line edges and
|
|
317
|
+
explicit break/continue (which target loop exit / loop top)."""
|
|
318
|
+
next_bb: int
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
@dataclass(frozen=True)
|
|
322
|
+
class Branch:
|
|
323
|
+
"""Conditional branch on `cond`."""
|
|
324
|
+
cond: TpyExpr
|
|
325
|
+
then_bb: int
|
|
326
|
+
else_bb: int
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@dataclass(frozen=True)
|
|
330
|
+
class Yield:
|
|
331
|
+
"""Suspension point. Saves state, returns Pending (async) / yield
|
|
332
|
+
value (generator); resume_bb is the entry point on resume."""
|
|
333
|
+
payload: SuspensionPayload
|
|
334
|
+
resume_bb: int
|
|
335
|
+
suspension_index: int # bijective with resume_bb among yields
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True)
|
|
339
|
+
class ReturnT:
|
|
340
|
+
"""Return from the function. Emit walks the active finally chain
|
|
341
|
+
before emitting the actual return."""
|
|
342
|
+
value: TpyExpr | None
|
|
343
|
+
return_stmt: TpyReturn # the original statement (for type info)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@dataclass(frozen=True)
|
|
347
|
+
class RaiseT:
|
|
348
|
+
"""Raise an exception. Emit walks the active finally chain via
|
|
349
|
+
the exception unwinding mechanism (no explicit chain run)."""
|
|
350
|
+
raise_stmt: TpyRaise
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@dataclass(frozen=True)
|
|
354
|
+
class Unreachable:
|
|
355
|
+
"""BB whose end is statically unreachable (e.g. infinite loop body
|
|
356
|
+
with no break/return)."""
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
@dataclass(frozen=True)
|
|
361
|
+
class AsyncForAdvance:
|
|
362
|
+
"""Terminator for the cond/advance BB of a CFG-decomposed for-loop
|
|
363
|
+
whose body contains await. Emit:
|
|
364
|
+
__for_r_<uid> = (*__for_itr_<uid>).__next__();
|
|
365
|
+
if (!(*__for_r_<uid>).has_value()) -> exhausted_bb
|
|
366
|
+
<loop_var> = ::tpy::unwrap_ref(*(*__for_r_<uid>));
|
|
367
|
+
-> has_value_bb
|
|
368
|
+
Two successors mirror Branch so case-entries pred-counting works."""
|
|
369
|
+
uid: int
|
|
370
|
+
stmt: TpyForEach
|
|
371
|
+
has_value_bb: int
|
|
372
|
+
exhausted_bb: int
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
@dataclass(frozen=True)
|
|
376
|
+
class MatchDispatch:
|
|
377
|
+
"""Terminator for a `match` whose arm bodies contain a suspension (H1).
|
|
378
|
+
|
|
379
|
+
The suspension-free dispatch (subject eval + every arm test + bindings
|
|
380
|
+
+ guards) is emitted by reusing the ordinary `gen_match`; each arm
|
|
381
|
+
*body* is routed back through the resumable walker (it may suspend),
|
|
382
|
+
so arm bodies live in the state machine while the dispatch keeps its
|
|
383
|
+
type-aware switch / if-elif shape. `arm_bbs[i]` is the entry BB of
|
|
384
|
+
`match_stmt.cases[i].body`; `join_bb` is the post-match continuation
|
|
385
|
+
(None when the match is exhaustive and every arm terminates, so no
|
|
386
|
+
fall-through exists)."""
|
|
387
|
+
match_stmt: TpyMatch
|
|
388
|
+
arm_bbs: tuple[int, ...]
|
|
389
|
+
join_bb: int | None
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
Terminator = Union[Fall, Branch, Yield, ReturnT, RaiseT, Unreachable,
|
|
393
|
+
AsyncForAdvance, MatchDispatch]
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
# -- Synthetic leaf-stmt types for CFG-lowered for-loops ----------------
|
|
397
|
+
|
|
398
|
+
@dataclass(frozen=True)
|
|
399
|
+
class AsyncForIterSetup:
|
|
400
|
+
"""Synthetic leaf stmt at the head of a CFG-decomposed for-loop.
|
|
401
|
+
|
|
402
|
+
Sync (`is_async=False`, v1.5 M3.1) emits:
|
|
403
|
+
`__for_itr_<uid> = ::tpy::__iter__(<iterable_expr>);`
|
|
404
|
+
Async (`is_async=True`, v1.5 M6) emits:
|
|
405
|
+
`__for_itr_<uid> = (<iterable_expr>).__aiter__();`
|
|
406
|
+
The frame field name is shared since only one of the two paths is
|
|
407
|
+
active per uid."""
|
|
408
|
+
uid: int
|
|
409
|
+
iterable_expr: TpyExpr
|
|
410
|
+
is_async: bool = False
|
|
411
|
+
# Loop element type, used by the range strategy's counter init (the
|
|
412
|
+
# emitter needs the C++ element type for the counter/stop casts).
|
|
413
|
+
elem_type: 'TpyType | None' = None
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
@dataclass(frozen=True)
|
|
417
|
+
class AsyncFinallyExit:
|
|
418
|
+
"""Synthetic leaf stmt at the single replay site of a CFG-decomposed
|
|
419
|
+
finally region (allocated as the finally's dedicated `finally_exit_bb`
|
|
420
|
+
so both normal fall-through and a return-in-finally drain through one
|
|
421
|
+
point). Two-stage emit -- pending-return check FIRST so a return in
|
|
422
|
+
the finally body wins over any in-flight exception (Python
|
|
423
|
+
semantics):
|
|
424
|
+
|
|
425
|
+
if (this-><pending_return_flag>) {
|
|
426
|
+
this-><pending_return_flag> = false;
|
|
427
|
+
this-><captured_exc_field> = nullptr; // swallow in-flight exc
|
|
428
|
+
<walk outer finally chain>
|
|
429
|
+
__state = S_DONE;
|
|
430
|
+
return Poll<T>::ready(std::move(this-><pending_return_slot>));
|
|
431
|
+
}
|
|
432
|
+
if (this-><captured_exc_field>) {
|
|
433
|
+
std::exception_ptr __tmp = this-><captured_exc_field>;
|
|
434
|
+
this-><captured_exc_field> = nullptr;
|
|
435
|
+
std::rethrow_exception(__tmp);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
Stage 1 fires when a `return` originating in the try body, any
|
|
439
|
+
handler body, OR the finally body itself set the pending flag. It
|
|
440
|
+
has two shapes depending on whether an enclosing CFG-finally region
|
|
441
|
+
is active at this exit site (see `_emit_async_finally_exit`):
|
|
442
|
+
* No enclosing CFG finally: replay locally -- walk the outer
|
|
443
|
+
helper chain, then emit Poll<T>::ready / StopIteration as shown
|
|
444
|
+
above (the deferred return is delivered as the coro's exit).
|
|
445
|
+
* Enclosing CFG finally present: forward -- move this exit's
|
|
446
|
+
parked value into the outer's `pending_return_slot`, set the
|
|
447
|
+
outer's `pending_return_flag`, walk helpers between this exit
|
|
448
|
+
and the outer's finally entry, then transition state to the
|
|
449
|
+
outer's `finally_entry_bb`. The outer's AsyncFinallyExit
|
|
450
|
+
eventually performs the local replay (or forwards again, for
|
|
451
|
+
deeper nesting).
|
|
452
|
+
The captured-exc clear-on-its-way-out implements the Python
|
|
453
|
+
return-in-finally-wins-over-raise rule. Stage 2 rethrows a saved
|
|
454
|
+
in-flight exception when no return is pending. Fields are cleared
|
|
455
|
+
after extraction so a re-entry to the same try (e.g. inside a loop)
|
|
456
|
+
doesn't carry over stale state.
|
|
457
|
+
|
|
458
|
+
`pending_return_flag` / `pending_return_slot` are None when no
|
|
459
|
+
`return` is reachable in try / handlers / finally (stage 1 omitted).
|
|
460
|
+
`pending_return_slot` is None for void async defs even when the flag
|
|
461
|
+
is set (the deferred return needs no value)."""
|
|
462
|
+
captured_exc_field: str
|
|
463
|
+
pending_return_flag: str | None = None
|
|
464
|
+
pending_return_slot: str | None = None
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
@dataclass(frozen=True)
|
|
468
|
+
class WithEnter:
|
|
469
|
+
"""Synthetic leaf stmt at the head of a CFG-decomposed sync `with`
|
|
470
|
+
body (sync `with X:` compiled inside an `async def`, M3.2). Real
|
|
471
|
+
Python `async with` uses its own setup synthetic (see AsyncWithSetup).
|
|
472
|
+
Emit:
|
|
473
|
+
__with_ctx_<n> = <context_expr>;
|
|
474
|
+
(*__with_ctx_<n>).__enter__(); # if target is None
|
|
475
|
+
<target> = (*__with_ctx_<n>).__enter__(); # otherwise
|
|
476
|
+
"""
|
|
477
|
+
ctx_n: int
|
|
478
|
+
item: TpyWithItem
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
@dataclass(frozen=True)
|
|
482
|
+
class AsyncWithSetup:
|
|
483
|
+
"""Synthetic leaf stmt at the head of a CFG-decomposed `async with`
|
|
484
|
+
region (v1.5 M5). Stores the context manager in a frame slot so the
|
|
485
|
+
subsequent `__aenter__` / `__aexit__` yields can dispatch through it.
|
|
486
|
+
|
|
487
|
+
Emit:
|
|
488
|
+
__with_ctx_<n> = <context_expr>;
|
|
489
|
+
|
|
490
|
+
(`__with_ctx_<n>` is the frame-hoisted `std::optional<CM>` field
|
|
491
|
+
allocated by the prescan; assigning a CM rvalue constructs in
|
|
492
|
+
place via `operator=`.) The Yield site immediately following
|
|
493
|
+
emplaces `__sub_<i>` with `(*__with_ctx_<n>, ...)` for the
|
|
494
|
+
`__aenter__` call; the finally body's Yield does the same for
|
|
495
|
+
`__aexit__(None, None, None)`.
|
|
496
|
+
"""
|
|
497
|
+
ctx_n: int
|
|
498
|
+
item: TpyWithItem
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
# -- Basic block
|
|
502
|
+
|
|
503
|
+
@dataclass
|
|
504
|
+
class BB:
|
|
505
|
+
"""A basic block.
|
|
506
|
+
|
|
507
|
+
`stmts` are leaf statements emitted via the normal StatementGenerator;
|
|
508
|
+
compound statements that *don't* transitively contain a suspension
|
|
509
|
+
stay as single elements here (lazy decomposition). Compound
|
|
510
|
+
statements that *do* contain suspensions are decomposed during CFG
|
|
511
|
+
construction and never appear in `stmts`.
|
|
512
|
+
|
|
513
|
+
`region_stack` is the (outermost ... innermost) list of regions
|
|
514
|
+
active when control enters this BB. Emit wraps the BB's emission
|
|
515
|
+
in the region_stack reconstructed as nested C++ try blocks.
|
|
516
|
+
|
|
517
|
+
`region_stack` is mutable (set by the builder's annotation pass).
|
|
518
|
+
"""
|
|
519
|
+
id: int
|
|
520
|
+
stmts: list[TpyStmt] = field(default_factory=list)
|
|
521
|
+
terminator: Terminator | None = None
|
|
522
|
+
region_stack: tuple[Region, ...] = ()
|
|
523
|
+
# Set by the (future) reachability pass; True iff this BB is reached
|
|
524
|
+
# only as the resume entry of a Yield (i.e. is a state target). Used
|
|
525
|
+
# by emit to decide whether to emit a case label.
|
|
526
|
+
is_resume_entry: bool = False
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
# -- Loop-context tracking (used during construction; not in the final CFG)
|
|
530
|
+
|
|
531
|
+
@dataclass
|
|
532
|
+
class _LoopCtx:
|
|
533
|
+
"""Pushed onto a stack while building loop bodies so break/continue
|
|
534
|
+
can resolve to the correct target BB.
|
|
535
|
+
|
|
536
|
+
`continue_bb` is the loop's check/step BB (for while: the condition
|
|
537
|
+
check; for for: the iterator advance). `break_bb` is the BB the
|
|
538
|
+
loop exits to. `regions_at_entry` is the region stack snapshot at
|
|
539
|
+
loop entry, used to compute the region delta on break/continue.
|
|
540
|
+
"""
|
|
541
|
+
continue_bb: int
|
|
542
|
+
break_bb: int
|
|
543
|
+
regions_at_entry: tuple[Region, ...]
|
|
544
|
+
|
|
545
|
+
|
|
546
|
+
# -- The CFG container
|
|
547
|
+
|
|
548
|
+
@dataclass
|
|
549
|
+
class CFG:
|
|
550
|
+
entry_bb: int
|
|
551
|
+
blocks: dict[int, BB]
|
|
552
|
+
yield_sites: list[Yield] = field(default_factory=list)
|
|
553
|
+
# Finally-body helper functions to emit as private members. Each
|
|
554
|
+
# entry is (helper_name, body_stmts). Order matters for stable
|
|
555
|
+
# output.
|
|
556
|
+
finally_helpers: list[tuple[str, list[TpyStmt]]] = field(default_factory=list)
|
|
557
|
+
# Cached emit-side maps. Populated lazily by the consumer (e.g.
|
|
558
|
+
# AsyncCoroCodegen) on first use; reused across struct-emit + poll-
|
|
559
|
+
# emit passes so we don't pay the O(N) predecessor walk + region-
|
|
560
|
+
# stack equality scan twice per function. The case-entries value
|
|
561
|
+
# type is consumer-defined (async emits StateLabel; future generator
|
|
562
|
+
# port may use a different shape) -- typed `dict[int, object]` to
|
|
563
|
+
# keep this module shape-neutral.
|
|
564
|
+
_case_entries_cache: dict[int, object] | None = field(default=None, repr=False)
|
|
565
|
+
_resume_to_yield_cache: dict[int, Yield] | None = field(default=None, repr=False)
|
|
566
|
+
|
|
567
|
+
def bb(self, bb_id: int) -> BB:
|
|
568
|
+
return self.blocks[bb_id]
|
|
569
|
+
|
|
570
|
+
def resume_to_yield(self) -> dict[int, Yield]:
|
|
571
|
+
"""`resume_bb -> Yield` map, built once and cached."""
|
|
572
|
+
if self._resume_to_yield_cache is None:
|
|
573
|
+
self._resume_to_yield_cache = {
|
|
574
|
+
y.resume_bb: y for y in self.yield_sites
|
|
575
|
+
}
|
|
576
|
+
return self._resume_to_yield_cache
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
# -- Builder
|
|
580
|
+
|
|
581
|
+
class CFGBuilder:
|
|
582
|
+
"""Walks a TpyFunction body and produces a CFG.
|
|
583
|
+
|
|
584
|
+
Usage:
|
|
585
|
+
builder = CFGBuilder()
|
|
586
|
+
cfg = builder.build(func_body)
|
|
587
|
+
|
|
588
|
+
The builder maintains a "current BB" while walking statements.
|
|
589
|
+
Compound statements that contain suspensions branch the CFG;
|
|
590
|
+
statements that don't are appended to the current BB as leaf
|
|
591
|
+
statements.
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
def __init__(self, payload_factory=None,
|
|
595
|
+
for_uid_map: 'dict[int, int] | None' = None,
|
|
596
|
+
with_uid_map: 'dict[int, list[int]] | None' = None,
|
|
597
|
+
try_finally_uid_map:
|
|
598
|
+
'dict[int, int] | None' = None,
|
|
599
|
+
func_returns_void: bool = False) -> None:
|
|
600
|
+
"""
|
|
601
|
+
`payload_factory(await_node, host_stmt, kind, bind_target,
|
|
602
|
+
return_stmt) -> AwaitPayload` is called for each top-level await
|
|
603
|
+
the builder encounters. The factory fills in mode +
|
|
604
|
+
sub_field_cpp_type, which are async-specific. If None, a
|
|
605
|
+
placeholder payload is created (suitable for testing only).
|
|
606
|
+
|
|
607
|
+
`for_uid_map` maps id(TpyForEach) -> uid for for-loops that the
|
|
608
|
+
caller has pre-scanned and registered with frame fields. Used by
|
|
609
|
+
`_build_for` to attach the correct uid to AsyncForIterSetup /
|
|
610
|
+
AsyncForAdvance. Missing entries cause `_build_for` to raise
|
|
611
|
+
_CFGNotYetSupported (the caller is expected to pre-register every
|
|
612
|
+
for-with-await loop).
|
|
613
|
+
|
|
614
|
+
`with_uid_map` maps id(TpyWith) -> list of ctx_n (one per WithItem,
|
|
615
|
+
in source order) for with-stmts pre-scanned and registered. Used
|
|
616
|
+
by `_build_with`. Missing entries cause `_build_with` to raise.
|
|
617
|
+
"""
|
|
618
|
+
self._blocks: dict[int, BB] = {}
|
|
619
|
+
self._next_bb_id: int = 0
|
|
620
|
+
self._yield_sites: list[Yield] = []
|
|
621
|
+
self._finally_helpers: list[tuple[str, list[TpyStmt]]] = []
|
|
622
|
+
self._next_finally_id: int = 0
|
|
623
|
+
# Construction-time stacks.
|
|
624
|
+
self._loop_stack: list[_LoopCtx] = []
|
|
625
|
+
self._region_stack: list[Region] = []
|
|
626
|
+
self._payload_factory = payload_factory
|
|
627
|
+
self._for_uid_map: dict[int, int] = for_uid_map or {}
|
|
628
|
+
self._with_uid_map: dict[int, list[int]] = with_uid_map or {}
|
|
629
|
+
self._try_finally_uid_map: dict[int, int] = try_finally_uid_map or {}
|
|
630
|
+
self._func_returns_void: bool = func_returns_void
|
|
631
|
+
# (id(region), id(handler)) -> handler-entry BB id. Populated
|
|
632
|
+
# by `_record_handler_entry` during try/except construction;
|
|
633
|
+
# read by emitters via `get_handler_entry`.
|
|
634
|
+
self._handler_entries: dict[tuple[int, int], int] = {}
|
|
635
|
+
|
|
636
|
+
# -- Public entry point ---------------------------------------------
|
|
637
|
+
|
|
638
|
+
def build(self, body: list[TpyStmt]) -> CFG:
|
|
639
|
+
"""Build a CFG from a resumable function body (async def or
|
|
640
|
+
generator). Shape-neutral: a `yield` is a suspension point
|
|
641
|
+
alongside `await`."""
|
|
642
|
+
entry = self._new_bb()
|
|
643
|
+
end = self._build_block(entry, body)
|
|
644
|
+
# If the body fell off the end without a terminator, mark the
|
|
645
|
+
# tail BB with Unreachable. The emitter inserts the appropriate
|
|
646
|
+
# "fell-through-without-returning" handling (Ready(unit) for
|
|
647
|
+
# `-> None`, panic otherwise).
|
|
648
|
+
if end is not None and self._blocks[end].terminator is None:
|
|
649
|
+
self._blocks[end].terminator = Unreachable()
|
|
650
|
+
cfg = CFG(
|
|
651
|
+
entry_bb=entry,
|
|
652
|
+
blocks=self._blocks,
|
|
653
|
+
yield_sites=self._yield_sites,
|
|
654
|
+
finally_helpers=self._finally_helpers,
|
|
655
|
+
)
|
|
656
|
+
self._mark_resume_entries(cfg)
|
|
657
|
+
return cfg
|
|
658
|
+
|
|
659
|
+
# -- BB helpers -----------------------------------------------------
|
|
660
|
+
|
|
661
|
+
def _new_bb(self) -> int:
|
|
662
|
+
bb_id = self._next_bb_id
|
|
663
|
+
self._next_bb_id += 1
|
|
664
|
+
bb = BB(id=bb_id, region_stack=tuple(self._region_stack))
|
|
665
|
+
self._blocks[bb_id] = bb
|
|
666
|
+
return bb_id
|
|
667
|
+
|
|
668
|
+
def _finish(self, bb_id: int, terminator: Terminator) -> None:
|
|
669
|
+
"""Set the terminator on a BB if it doesn't already have one.
|
|
670
|
+
A BB ending in return/raise/yield/branch is sealed; subsequent
|
|
671
|
+
statements in the source belong to a fresh BB."""
|
|
672
|
+
bb = self._blocks[bb_id]
|
|
673
|
+
if bb.terminator is None:
|
|
674
|
+
bb.terminator = terminator
|
|
675
|
+
|
|
676
|
+
def _is_sealed(self, bb_id: int) -> bool:
|
|
677
|
+
return self._blocks[bb_id].terminator is not None
|
|
678
|
+
|
|
679
|
+
# -- Body walker ----------------------------------------------------
|
|
680
|
+
|
|
681
|
+
def _build_block(self, entry_bb: int, stmts: list[TpyStmt]) -> int | None:
|
|
682
|
+
"""Walk a statement list, appending to / forking from entry_bb.
|
|
683
|
+
Returns the BB id where control flows out (None if the block
|
|
684
|
+
terminates unconditionally via return/raise/break/continue)."""
|
|
685
|
+
cur = entry_bb
|
|
686
|
+
for stmt in stmts:
|
|
687
|
+
if self._is_sealed(cur):
|
|
688
|
+
# Source-level dead code after a return/raise/break/
|
|
689
|
+
# continue. Emit into a fresh BB that's only reachable
|
|
690
|
+
# from nothing; it'll be DCE'd by the emitter.
|
|
691
|
+
cur = self._new_bb()
|
|
692
|
+
cur_after = self._build_stmt(cur, stmt)
|
|
693
|
+
if cur_after is None:
|
|
694
|
+
return None
|
|
695
|
+
cur = cur_after
|
|
696
|
+
return cur
|
|
697
|
+
|
|
698
|
+
def _build_stmt(self, cur: int, stmt: TpyStmt) -> int | None:
|
|
699
|
+
"""Process one statement. Returns the BB id where control flows
|
|
700
|
+
out of this statement (None for unconditional terminators)."""
|
|
701
|
+
# Statements with explicit terminator semantics.
|
|
702
|
+
if isinstance(stmt, TpyReturn):
|
|
703
|
+
return self._build_return(cur, stmt)
|
|
704
|
+
if isinstance(stmt, TpyRaise):
|
|
705
|
+
self._finish(cur, RaiseT(raise_stmt=stmt))
|
|
706
|
+
return None
|
|
707
|
+
if isinstance(stmt, TpyBreak):
|
|
708
|
+
self._build_break(cur)
|
|
709
|
+
return None
|
|
710
|
+
if isinstance(stmt, TpyContinue):
|
|
711
|
+
self._build_continue(cur)
|
|
712
|
+
return None
|
|
713
|
+
# Statements that may host a top-level await.
|
|
714
|
+
if self._stmt_has_top_level_await(stmt):
|
|
715
|
+
return self._build_top_level_await_stmt(cur, stmt)
|
|
716
|
+
# A `yield` is a generator suspension at statement position.
|
|
717
|
+
if isinstance(stmt, TpyYield):
|
|
718
|
+
return self._build_yield_stmt(cur, stmt)
|
|
719
|
+
# A non-loop compound (if/try/with) inside a decomposed loop
|
|
720
|
+
# must be decomposed if it contains break/continue targeting
|
|
721
|
+
# the outer loop -- otherwise the break/continue would become
|
|
722
|
+
# C++ break/continue inside our state-machine switch instead
|
|
723
|
+
# of a CFG state transition.
|
|
724
|
+
force_loop_decomp = (self._loop_stack
|
|
725
|
+
and _stmt_has_unbound_loop_transfer(stmt))
|
|
726
|
+
# Compound statements: decompose if they contain a nested
|
|
727
|
+
# suspension OR an unbound break/continue inside a loop.
|
|
728
|
+
if isinstance(stmt, TpyIf):
|
|
729
|
+
if _stmt_has_any_suspension(stmt) or force_loop_decomp:
|
|
730
|
+
return self._build_if(cur, stmt)
|
|
731
|
+
elif isinstance(stmt, TpyWhile):
|
|
732
|
+
if _stmt_has_any_suspension(stmt):
|
|
733
|
+
return self._build_while(cur, stmt)
|
|
734
|
+
elif isinstance(stmt, TpyForEach):
|
|
735
|
+
if stmt.is_async or _stmt_has_any_suspension(stmt):
|
|
736
|
+
return self._build_for(cur, stmt)
|
|
737
|
+
elif isinstance(stmt, TpyTry):
|
|
738
|
+
if _stmt_has_any_suspension(stmt) or force_loop_decomp:
|
|
739
|
+
return self._build_try(cur, stmt)
|
|
740
|
+
elif isinstance(stmt, TpyWith):
|
|
741
|
+
if _stmt_has_any_suspension(stmt) or force_loop_decomp:
|
|
742
|
+
return self._build_with(cur, stmt)
|
|
743
|
+
elif isinstance(stmt, TpyMatch):
|
|
744
|
+
if _stmt_has_any_suspension(stmt) or force_loop_decomp:
|
|
745
|
+
return self._build_match(cur, stmt)
|
|
746
|
+
# Leaf statement (or compound without suspensions). If a statement
|
|
747
|
+
# reaches this point STILL containing a suspension, it's a statement
|
|
748
|
+
# kind the builder doesn't decompose (if/while/for/try/with/match all
|
|
749
|
+
# are). Appending it as a leaf would emit the nested `await`/`yield`
|
|
750
|
+
# as straight-line code -- silently wrong for async, and for a
|
|
751
|
+
# generator it would collide with the resumable state enum. Refuse so
|
|
752
|
+
# the build surfaces a clean CodeGenError instead of a miscompile.
|
|
753
|
+
if _stmt_has_any_suspension(stmt):
|
|
754
|
+
raise _CFGNotYetSupported(
|
|
755
|
+
"a suspension (`await`/`yield`) inside this statement is "
|
|
756
|
+
"not yet supported by the resumable lowering (the statement "
|
|
757
|
+
"kind is not decomposed by the CFG builder).",
|
|
758
|
+
loc=getattr(stmt, "loc", None),
|
|
759
|
+
)
|
|
760
|
+
self._blocks[cur].stmts.append(stmt)
|
|
761
|
+
return cur
|
|
762
|
+
|
|
763
|
+
# -- Top-level await on assign/vardecl/return/expr-stmt -------------
|
|
764
|
+
|
|
765
|
+
def _stmt_has_top_level_await(self, stmt: TpyStmt) -> bool:
|
|
766
|
+
return _top_level_await_in(stmt) is not None
|
|
767
|
+
|
|
768
|
+
def _build_top_level_await_stmt(self, cur: int, stmt: TpyStmt) -> int | None:
|
|
769
|
+
"""Convert a statement whose top-level expression slot is a
|
|
770
|
+
TpyAwait into a Yield terminator. The bind / discard / return
|
|
771
|
+
action runs in the resume BB."""
|
|
772
|
+
await_node = _top_level_await_in(stmt)
|
|
773
|
+
assert await_node is not None
|
|
774
|
+
kind, bind_target, return_stmt = _classify_await_position(stmt, await_node)
|
|
775
|
+
if self._payload_factory is not None:
|
|
776
|
+
payload = self._payload_factory(
|
|
777
|
+
await_node, stmt, kind, bind_target, return_stmt)
|
|
778
|
+
else:
|
|
779
|
+
payload = AwaitPayload(
|
|
780
|
+
mode=AwaitMode.INLINE,
|
|
781
|
+
sub_field_cpp_type="",
|
|
782
|
+
operand_expr=await_node.value,
|
|
783
|
+
kind=kind,
|
|
784
|
+
bind_target=bind_target,
|
|
785
|
+
return_stmt=return_stmt,
|
|
786
|
+
host_stmt=stmt,
|
|
787
|
+
await_node=await_node,
|
|
788
|
+
)
|
|
789
|
+
suspension_idx = len(self._yield_sites)
|
|
790
|
+
resume_bb = self._new_bb()
|
|
791
|
+
terminator = Yield(
|
|
792
|
+
payload=payload,
|
|
793
|
+
resume_bb=resume_bb,
|
|
794
|
+
suspension_index=suspension_idx,
|
|
795
|
+
)
|
|
796
|
+
self._finish(cur, terminator)
|
|
797
|
+
self._yield_sites.append(terminator)
|
|
798
|
+
# If the await's host stmt was a TpyReturn, the resume case
|
|
799
|
+
# body runs the finally chain and returns; control does not
|
|
800
|
+
# flow past it.
|
|
801
|
+
if kind is AwaitKind.RETURN:
|
|
802
|
+
self._finish(resume_bb, ReturnT(value=None, return_stmt=return_stmt))
|
|
803
|
+
return None
|
|
804
|
+
return resume_bb
|
|
805
|
+
|
|
806
|
+
def _build_yield_stmt(self, cur: int, stmt: TpyYield) -> int | None:
|
|
807
|
+
"""Convert a `yield` statement into a Yield terminator. Unlike an
|
|
808
|
+
`await`, a yield pushes its value OUT to the caller and resumes
|
|
809
|
+
in place -- there is no sub-future to poll and (until `send()`)
|
|
810
|
+
no value bound back in, so the resume BB simply continues after
|
|
811
|
+
the yield. The generator emitter reads `value_expr` / `yield_stmt`
|
|
812
|
+
off the payload to produce the yielded value."""
|
|
813
|
+
payload = YieldPayload(value_expr=stmt.value, yield_stmt=stmt)
|
|
814
|
+
suspension_idx = len(self._yield_sites)
|
|
815
|
+
resume_bb = self._new_bb()
|
|
816
|
+
terminator = Yield(
|
|
817
|
+
payload=payload,
|
|
818
|
+
resume_bb=resume_bb,
|
|
819
|
+
suspension_index=suspension_idx,
|
|
820
|
+
)
|
|
821
|
+
self._finish(cur, terminator)
|
|
822
|
+
self._yield_sites.append(terminator)
|
|
823
|
+
return resume_bb
|
|
824
|
+
|
|
825
|
+
def _build_return(self, cur: int, stmt: TpyReturn) -> int | None:
|
|
826
|
+
# `return await ...` is the RETURN await kind; routed through
|
|
827
|
+
# _build_top_level_await_stmt.
|
|
828
|
+
if isinstance(stmt.value, TpyAwait):
|
|
829
|
+
return self._build_top_level_await_stmt(cur, stmt)
|
|
830
|
+
self._finish(cur, ReturnT(value=stmt.value, return_stmt=stmt))
|
|
831
|
+
return None
|
|
832
|
+
|
|
833
|
+
# -- break / continue -----------------------------------------------
|
|
834
|
+
|
|
835
|
+
def _build_break(self, cur: int) -> None:
|
|
836
|
+
if not self._loop_stack:
|
|
837
|
+
# Sema would have rejected; treat as unreachable.
|
|
838
|
+
self._finish(cur, Unreachable())
|
|
839
|
+
return
|
|
840
|
+
target = self._loop_stack[-1].break_bb
|
|
841
|
+
self._finish(cur, Fall(next_bb=target))
|
|
842
|
+
|
|
843
|
+
def _build_continue(self, cur: int) -> None:
|
|
844
|
+
if not self._loop_stack:
|
|
845
|
+
self._finish(cur, Unreachable())
|
|
846
|
+
return
|
|
847
|
+
target = self._loop_stack[-1].continue_bb
|
|
848
|
+
self._finish(cur, Fall(next_bb=target))
|
|
849
|
+
|
|
850
|
+
# -- if/else --------------------------------------------------------
|
|
851
|
+
|
|
852
|
+
def _build_if(self, cur: int, stmt: TpyIf) -> int | None:
|
|
853
|
+
then_bb = self._new_bb()
|
|
854
|
+
else_bb = self._new_bb()
|
|
855
|
+
join_bb = self._new_bb()
|
|
856
|
+
self._finish(cur, Branch(cond=stmt.condition, then_bb=then_bb,
|
|
857
|
+
else_bb=else_bb))
|
|
858
|
+
then_end = self._build_block(then_bb, stmt.then_body)
|
|
859
|
+
if then_end is not None:
|
|
860
|
+
self._finish(then_end, Fall(next_bb=join_bb))
|
|
861
|
+
else_end = self._build_block(else_bb, stmt.else_body)
|
|
862
|
+
if else_end is not None:
|
|
863
|
+
self._finish(else_end, Fall(next_bb=join_bb))
|
|
864
|
+
# If both branches terminate, the join is unreachable.
|
|
865
|
+
if then_end is None and else_end is None:
|
|
866
|
+
return None
|
|
867
|
+
return join_bb
|
|
868
|
+
|
|
869
|
+
# -- while ----------------------------------------------------------
|
|
870
|
+
|
|
871
|
+
def _build_while(self, cur: int, stmt: TpyWhile) -> int | None:
|
|
872
|
+
check_bb = self._new_bb()
|
|
873
|
+
body_bb = self._new_bb()
|
|
874
|
+
# `exit_bb` is the NORMAL-exit target (condition false). When there
|
|
875
|
+
# is an `else` clause it runs there; `break` instead targets a
|
|
876
|
+
# separate `after_bb` that skips the else (Python loop-else
|
|
877
|
+
# semantics). With no else, `after_bb` IS `exit_bb` (no extra BB,
|
|
878
|
+
# so non-else loops are structurally unchanged).
|
|
879
|
+
exit_bb = self._new_bb()
|
|
880
|
+
self._finish(cur, Fall(next_bb=check_bb))
|
|
881
|
+
self._finish(check_bb, Branch(cond=stmt.condition, then_bb=body_bb,
|
|
882
|
+
else_bb=exit_bb))
|
|
883
|
+
after_bb = self._new_bb() if stmt.orelse else exit_bb
|
|
884
|
+
self._loop_stack.append(_LoopCtx(
|
|
885
|
+
continue_bb=check_bb,
|
|
886
|
+
break_bb=after_bb,
|
|
887
|
+
regions_at_entry=tuple(self._region_stack),
|
|
888
|
+
))
|
|
889
|
+
try:
|
|
890
|
+
body_end = self._build_block(body_bb, stmt.body)
|
|
891
|
+
if body_end is not None:
|
|
892
|
+
self._finish(body_end, Fall(next_bb=check_bb))
|
|
893
|
+
finally:
|
|
894
|
+
self._loop_stack.pop()
|
|
895
|
+
if stmt.orelse:
|
|
896
|
+
# else runs on the normal-exit edge, then falls to after_bb;
|
|
897
|
+
# break edges (-> after_bb) skip it. The else body is a regular
|
|
898
|
+
# region so it may itself contain suspensions.
|
|
899
|
+
else_end = self._build_block(exit_bb, stmt.orelse)
|
|
900
|
+
if else_end is not None:
|
|
901
|
+
self._finish(else_end, Fall(next_bb=after_bb))
|
|
902
|
+
return after_bb
|
|
903
|
+
return exit_bb
|
|
904
|
+
|
|
905
|
+
# -- for ------------------------------------------------------------
|
|
906
|
+
|
|
907
|
+
def _build_for(self, cur: int, stmt: TpyForEach) -> int | None:
|
|
908
|
+
if stmt.is_async:
|
|
909
|
+
return self._build_async_for(cur, stmt)
|
|
910
|
+
# Universal iter/next lowering. The for-loop becomes:
|
|
911
|
+
# iter_init -> cond_advance --(has_value)-> body BBs --(fall)-> cond_advance
|
|
912
|
+
# --(exhausted)-> exit
|
|
913
|
+
# The cond/advance BB carries an AsyncForAdvance terminator
|
|
914
|
+
# which expands at emit to next() + has_value check + bind.
|
|
915
|
+
uid = self._for_uid_map.get(id(stmt))
|
|
916
|
+
if uid is None:
|
|
917
|
+
raise _CFGNotYetSupported(
|
|
918
|
+
"for-loop with await reached CFG builder without a "
|
|
919
|
+
"registered uid (internal: pre-scan missed this loop).",
|
|
920
|
+
loc=stmt.loc,
|
|
921
|
+
)
|
|
922
|
+
iter_init_bb = self._new_bb()
|
|
923
|
+
cond_bb = self._new_bb()
|
|
924
|
+
body_bb = self._new_bb()
|
|
925
|
+
# `exit_bb` is the EXHAUSTED (normal-exit) target; the `else` clause
|
|
926
|
+
# runs there. `break` targets `after_bb` (skips else). No else ->
|
|
927
|
+
# after_bb IS exit_bb (non-else for-loops unchanged).
|
|
928
|
+
exit_bb = self._new_bb()
|
|
929
|
+
# Fall into iter init.
|
|
930
|
+
self._finish(cur, Fall(next_bb=iter_init_bb))
|
|
931
|
+
# iter_init_bb: setup, fall to cond.
|
|
932
|
+
self._blocks[iter_init_bb].stmts.append(
|
|
933
|
+
AsyncForIterSetup(uid=uid, iterable_expr=stmt.iterable,
|
|
934
|
+
elem_type=stmt.elem_type))
|
|
935
|
+
self._finish(iter_init_bb, Fall(next_bb=cond_bb))
|
|
936
|
+
# cond_bb: AsyncForAdvance terminator (advance + branch).
|
|
937
|
+
self._finish(cond_bb, AsyncForAdvance(
|
|
938
|
+
uid=uid, stmt=stmt,
|
|
939
|
+
has_value_bb=body_bb, exhausted_bb=exit_bb))
|
|
940
|
+
after_bb = self._new_bb() if stmt.orelse else exit_bb
|
|
941
|
+
# body: continue=cond_bb, break=after_bb (skips the else).
|
|
942
|
+
self._loop_stack.append(_LoopCtx(
|
|
943
|
+
continue_bb=cond_bb,
|
|
944
|
+
break_bb=after_bb,
|
|
945
|
+
regions_at_entry=tuple(self._region_stack),
|
|
946
|
+
))
|
|
947
|
+
try:
|
|
948
|
+
body_end = self._build_block(body_bb, stmt.body)
|
|
949
|
+
if body_end is not None:
|
|
950
|
+
self._finish(body_end, Fall(next_bb=cond_bb))
|
|
951
|
+
finally:
|
|
952
|
+
self._loop_stack.pop()
|
|
953
|
+
if stmt.orelse:
|
|
954
|
+
else_end = self._build_block(exit_bb, stmt.orelse)
|
|
955
|
+
if else_end is not None:
|
|
956
|
+
self._finish(else_end, Fall(next_bb=after_bb))
|
|
957
|
+
return after_bb
|
|
958
|
+
return exit_bb
|
|
959
|
+
|
|
960
|
+
def _build_async_for(self, cur: int, stmt: TpyForEach) -> int | None:
|
|
961
|
+
"""Lower `async for y in ait: <body>` (v1.5 M6).
|
|
962
|
+
|
|
963
|
+
Shape:
|
|
964
|
+
iter_init_bb (AsyncForIterSetup(is_async=True))
|
|
965
|
+
-> TryRegion[ExceptRegion(StopAsyncIteration -> break)] {
|
|
966
|
+
cond_bb: Yield(await __aiter.__anext__(), bind y)
|
|
967
|
+
resume_bb: (binds y, falls out of try)
|
|
968
|
+
}
|
|
969
|
+
(pop TryRegion)
|
|
970
|
+
body_bb: <user body>, Fall -> cond_bb
|
|
971
|
+
handler -> exit_bb (via the loop's break_bb)
|
|
972
|
+
|
|
973
|
+
The TryRegion wraps ONLY the cond/resume pair, NOT the body.
|
|
974
|
+
A StopAsyncIteration from the body must propagate up like any
|
|
975
|
+
other exception, so the body's case bodies must not be wrapped
|
|
976
|
+
in the auto-catch.
|
|
977
|
+
|
|
978
|
+
Parser rejects `else:` on async-for, so no orelse handling here.
|
|
979
|
+
"""
|
|
980
|
+
uid = self._for_uid_map.get(id(stmt))
|
|
981
|
+
if uid is None:
|
|
982
|
+
raise _CFGNotYetSupported(
|
|
983
|
+
"async-for reached CFG builder without a registered "
|
|
984
|
+
"uid (internal: pre-scan missed this loop).",
|
|
985
|
+
loc=stmt.loc,
|
|
986
|
+
)
|
|
987
|
+
# body_bb / exit_bb are created OUTSIDE the TryRegion push so a
|
|
988
|
+
# StopAsyncIteration thrown from the body propagates rather than
|
|
989
|
+
# being silently caught by the loop's auto-handler. iter_init_bb
|
|
990
|
+
# likewise stays outside (the __aiter__ call shouldn't throw
|
|
991
|
+
# StopAsyncIteration, and a try region around it would distort
|
|
992
|
+
# the case structure). cond_bb / resume_bb / handler body BB are
|
|
993
|
+
# created below INSIDE the push so the emitter wraps their case
|
|
994
|
+
# bodies in `catch (StopAsyncIteration&)`.
|
|
995
|
+
iter_init_bb = self._new_bb()
|
|
996
|
+
body_bb = self._new_bb()
|
|
997
|
+
exit_bb = self._new_bb()
|
|
998
|
+
|
|
999
|
+
self._finish(cur, Fall(next_bb=iter_init_bb))
|
|
1000
|
+
self._blocks[iter_init_bb].stmts.append(
|
|
1001
|
+
AsyncForIterSetup(uid=uid, iterable_expr=stmt.iterable,
|
|
1002
|
+
is_async=True))
|
|
1003
|
+
|
|
1004
|
+
# The handler body is a synthesized TpyBreak -- _build_block
|
|
1005
|
+
# routes it through _loop_stack to the loop's break_bb, so
|
|
1006
|
+
# _loop_stack must be active while the handler is built.
|
|
1007
|
+
handler = TpyExceptHandler(
|
|
1008
|
+
exception_type="StopAsyncIteration",
|
|
1009
|
+
binding=None,
|
|
1010
|
+
body=[TpyBreak(loc=stmt.loc)],
|
|
1011
|
+
loc=stmt.loc,
|
|
1012
|
+
)
|
|
1013
|
+
try_region = TryRegion(
|
|
1014
|
+
handlers=(handler,),
|
|
1015
|
+
finally_helper_name=None,
|
|
1016
|
+
tier="throw",
|
|
1017
|
+
loc_source=stmt,
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
self._region_stack.append(try_region)
|
|
1021
|
+
cond_bb = self._new_bb()
|
|
1022
|
+
resume_bb = self._new_bb()
|
|
1023
|
+
self._loop_stack.append(_LoopCtx(
|
|
1024
|
+
continue_bb=cond_bb,
|
|
1025
|
+
break_bb=exit_bb,
|
|
1026
|
+
regions_at_entry=tuple(self._region_stack),
|
|
1027
|
+
))
|
|
1028
|
+
try:
|
|
1029
|
+
self._finish(iter_init_bb, Fall(next_bb=cond_bb))
|
|
1030
|
+
dummy_await = TpyAwait(value=stmt.iterable, loc=stmt.loc)
|
|
1031
|
+
payload = AwaitPayload(
|
|
1032
|
+
mode=AwaitMode.INLINE,
|
|
1033
|
+
sub_field_cpp_type="", # filled in by struct emit
|
|
1034
|
+
operand_expr=stmt.iterable,
|
|
1035
|
+
kind=AwaitKind.ASSIGN,
|
|
1036
|
+
bind_target=stmt.var,
|
|
1037
|
+
return_stmt=None,
|
|
1038
|
+
host_stmt=stmt,
|
|
1039
|
+
await_node=dummy_await,
|
|
1040
|
+
async_for_uid=uid,
|
|
1041
|
+
)
|
|
1042
|
+
yield_term = Yield(
|
|
1043
|
+
payload=payload,
|
|
1044
|
+
resume_bb=resume_bb,
|
|
1045
|
+
suspension_index=len(self._yield_sites),
|
|
1046
|
+
)
|
|
1047
|
+
self._finish(cond_bb, yield_term)
|
|
1048
|
+
self._yield_sites.append(yield_term)
|
|
1049
|
+
# Build the synthesized except handler body (single TpyBreak)
|
|
1050
|
+
# while the TryRegion is still on the stack so the
|
|
1051
|
+
# ExceptRegion has the right parent.
|
|
1052
|
+
except_region = ExceptRegion(
|
|
1053
|
+
handler=handler,
|
|
1054
|
+
parent_finally=None,
|
|
1055
|
+
loc_source=stmt,
|
|
1056
|
+
)
|
|
1057
|
+
self._region_stack.append(except_region)
|
|
1058
|
+
try:
|
|
1059
|
+
handler_entry = self._new_bb()
|
|
1060
|
+
handler_end = self._build_block(handler_entry, handler.body)
|
|
1061
|
+
assert handler_end is None # TpyBreak terminates
|
|
1062
|
+
self._record_handler_entry(try_region, handler, handler_entry)
|
|
1063
|
+
finally:
|
|
1064
|
+
self._region_stack.pop()
|
|
1065
|
+
finally:
|
|
1066
|
+
self._region_stack.pop()
|
|
1067
|
+
|
|
1068
|
+
# body_bb sits outside the TryRegion -- a user `await` in the
|
|
1069
|
+
# body whose result throws StopAsyncIteration propagates up
|
|
1070
|
+
# rather than being silently swallowed.
|
|
1071
|
+
self._finish(resume_bb, Fall(next_bb=body_bb))
|
|
1072
|
+
try:
|
|
1073
|
+
body_end = self._build_block(body_bb, stmt.body)
|
|
1074
|
+
if body_end is not None:
|
|
1075
|
+
self._finish(body_end, Fall(next_bb=cond_bb))
|
|
1076
|
+
finally:
|
|
1077
|
+
self._loop_stack.pop()
|
|
1078
|
+
|
|
1079
|
+
return exit_bb
|
|
1080
|
+
|
|
1081
|
+
# -- try/except/finally ---------------------------------------------
|
|
1082
|
+
|
|
1083
|
+
def _build_try(self, cur: int, stmt: TpyTry) -> int | None:
|
|
1084
|
+
finally_name: str | None = None
|
|
1085
|
+
finally_entry_bb: int | None = None
|
|
1086
|
+
captured_exc_field: str | None = None
|
|
1087
|
+
pending_return_flag: str | None = None
|
|
1088
|
+
pending_return_slot: str | None = None
|
|
1089
|
+
finally_async = bool(stmt.finally_body) and _stmts_have_any_suspension(
|
|
1090
|
+
stmt.finally_body)
|
|
1091
|
+
if stmt.finally_body and not finally_async:
|
|
1092
|
+
finally_name = f"__finally_{self._next_finally_id}"
|
|
1093
|
+
self._next_finally_id += 1
|
|
1094
|
+
self._finally_helpers.append((finally_name, list(stmt.finally_body)))
|
|
1095
|
+
elif finally_async:
|
|
1096
|
+
# CFG-based finally. Pre-scan assigns an exception-slot uid
|
|
1097
|
+
# and (if returns are reachable in any of try / handler /
|
|
1098
|
+
# finally) a pending-return slot. Nesting two CFG-based
|
|
1099
|
+
# finally regions is supported: the inner AsyncFinallyExit
|
|
1100
|
+
# forwards its pending state into the outer's parking slot
|
|
1101
|
+
# (see `_emit_async_finally_exit`).
|
|
1102
|
+
uid = self._try_finally_uid_map.get(id(stmt))
|
|
1103
|
+
if uid is None:
|
|
1104
|
+
raise _CFGNotYetSupported(
|
|
1105
|
+
"try-finally-with-await reached CFG builder "
|
|
1106
|
+
"without a registered exception-slot uid "
|
|
1107
|
+
"(internal: pre-scan missed it).",
|
|
1108
|
+
loc=stmt.loc,
|
|
1109
|
+
)
|
|
1110
|
+
captured_exc_field = f"__finally_exc_{uid}"
|
|
1111
|
+
# Pending-return slot: allocated when there's a reachable
|
|
1112
|
+
# `return` anywhere the CFG-based finally can intercept --
|
|
1113
|
+
# try body, any handler body, or the finally body itself
|
|
1114
|
+
# (the FinallyRegion mirrors the flag/slot so a return inside
|
|
1115
|
+
# the finally body parks into the same place).
|
|
1116
|
+
# `pending_return_slot` is None for void async defs (the
|
|
1117
|
+
# flag alone suffices).
|
|
1118
|
+
try_has_return = (_stmts_have_any_return(stmt.try_body)
|
|
1119
|
+
or any(_stmts_have_any_return(h.body)
|
|
1120
|
+
for h in stmt.handlers)
|
|
1121
|
+
or _stmts_have_any_return(stmt.finally_body))
|
|
1122
|
+
if try_has_return:
|
|
1123
|
+
pending_return_flag = f"__finally_pending_{uid}"
|
|
1124
|
+
# Void async defs need only the flag (no value to park);
|
|
1125
|
+
# leave `pending_return_slot` None so emit sites can
|
|
1126
|
+
# consult one source of truth (the slot name) instead
|
|
1127
|
+
# of also re-deriving void-ness.
|
|
1128
|
+
if self._func_returns_void:
|
|
1129
|
+
pending_return_slot = None
|
|
1130
|
+
else:
|
|
1131
|
+
pending_return_slot = f"__finally_ret_{uid}"
|
|
1132
|
+
else:
|
|
1133
|
+
pending_return_flag = None
|
|
1134
|
+
pending_return_slot = None
|
|
1135
|
+
|
|
1136
|
+
# Pre-allocate finally_entry_bb + finally_exit_bb (CFG-based
|
|
1137
|
+
# finally only) so TryRegion / FinallyRegion can carry them.
|
|
1138
|
+
# `finally_exit_bb` is the single replay site holding the
|
|
1139
|
+
# AsyncFinallyExit synth -- both normal fall-through from the
|
|
1140
|
+
# finally body and a return-in-finally jump here, which gives
|
|
1141
|
+
# the deferred-return replay a home outside the body's last BB.
|
|
1142
|
+
finally_region: FinallyRegion | None = None
|
|
1143
|
+
finally_exit_bb: int | None = None
|
|
1144
|
+
if finally_async:
|
|
1145
|
+
assert captured_exc_field is not None
|
|
1146
|
+
# finally_exit_bb is OUTSIDE the FinallyRegion -- by the time
|
|
1147
|
+
# AsyncFinallyExit runs, the finally body is done.
|
|
1148
|
+
finally_exit_bb = self._new_bb()
|
|
1149
|
+
finally_region = FinallyRegion(
|
|
1150
|
+
helper_name=captured_exc_field,
|
|
1151
|
+
loc_source=stmt,
|
|
1152
|
+
pending_return_flag=pending_return_flag,
|
|
1153
|
+
pending_return_slot=pending_return_slot,
|
|
1154
|
+
finally_exit_bb=finally_exit_bb,
|
|
1155
|
+
)
|
|
1156
|
+
self._region_stack.append(finally_region)
|
|
1157
|
+
finally_entry_bb = self._new_bb()
|
|
1158
|
+
self._region_stack.pop()
|
|
1159
|
+
|
|
1160
|
+
try_region = TryRegion(
|
|
1161
|
+
handlers=tuple(stmt.handlers),
|
|
1162
|
+
finally_helper_name=finally_name,
|
|
1163
|
+
tier=stmt.tier or "throw",
|
|
1164
|
+
loc_source=stmt,
|
|
1165
|
+
finally_entry_bb=finally_entry_bb,
|
|
1166
|
+
captured_exc_field=captured_exc_field,
|
|
1167
|
+
pending_return_flag=pending_return_flag,
|
|
1168
|
+
pending_return_slot=pending_return_slot,
|
|
1169
|
+
)
|
|
1170
|
+
# join_bb is outside the try frame.
|
|
1171
|
+
join_bb = self._new_bb()
|
|
1172
|
+
|
|
1173
|
+
# Target for normal try-body exit: finally_entry when CFG-based
|
|
1174
|
+
# finally (so finally body runs before reaching join); join
|
|
1175
|
+
# otherwise.
|
|
1176
|
+
normal_exit_target = (finally_entry_bb if finally_async
|
|
1177
|
+
else join_bb)
|
|
1178
|
+
|
|
1179
|
+
# Try body BBs live inside the TryRegion. Push the region BEFORE
|
|
1180
|
+
# creating them so their region_stack captures correctly.
|
|
1181
|
+
self._region_stack.append(try_region)
|
|
1182
|
+
try:
|
|
1183
|
+
try_body_bb = self._new_bb()
|
|
1184
|
+
self._finish(cur, Fall(next_bb=try_body_bb))
|
|
1185
|
+
try_end = self._build_block(try_body_bb, stmt.try_body)
|
|
1186
|
+
if try_end is not None:
|
|
1187
|
+
if stmt.else_body:
|
|
1188
|
+
else_end = self._build_block(try_end, stmt.else_body)
|
|
1189
|
+
if else_end is not None:
|
|
1190
|
+
self._finish(else_end,
|
|
1191
|
+
Fall(next_bb=normal_exit_target))
|
|
1192
|
+
else:
|
|
1193
|
+
self._finish(try_end,
|
|
1194
|
+
Fall(next_bb=normal_exit_target))
|
|
1195
|
+
finally:
|
|
1196
|
+
self._region_stack.pop()
|
|
1197
|
+
|
|
1198
|
+
# Except-handler bodies: the corresponding TryRegion is no
|
|
1199
|
+
# longer active inside the handler (the throw has been caught);
|
|
1200
|
+
# the ExceptRegion takes its place so emit knows we're in a
|
|
1201
|
+
# handler body (for sub-future reset, exception-binding scope).
|
|
1202
|
+
# When the parent try has a CFG-based finally, the
|
|
1203
|
+
# ExceptRegion also carries the parent's captured_exc /
|
|
1204
|
+
# finally_entry so emit can route raises and returns inside
|
|
1205
|
+
# the handler body through the finally region.
|
|
1206
|
+
for handler in stmt.handlers:
|
|
1207
|
+
except_region = ExceptRegion(
|
|
1208
|
+
handler=handler,
|
|
1209
|
+
parent_finally=try_region.finally_helper_name,
|
|
1210
|
+
parent_finally_entry_bb=try_region.finally_entry_bb,
|
|
1211
|
+
parent_pending_return_flag=try_region.pending_return_flag,
|
|
1212
|
+
parent_pending_return_slot=try_region.pending_return_slot,
|
|
1213
|
+
loc_source=stmt,
|
|
1214
|
+
)
|
|
1215
|
+
self._region_stack.append(except_region)
|
|
1216
|
+
try:
|
|
1217
|
+
handler_entry = self._new_bb()
|
|
1218
|
+
handler_end = self._build_block(handler_entry, handler.body)
|
|
1219
|
+
if handler_end is not None:
|
|
1220
|
+
# Handler's normal exit: when finally is CFG-based,
|
|
1221
|
+
# route through finally entry so the finally body
|
|
1222
|
+
# runs before reaching the post-try join.
|
|
1223
|
+
self._finish(handler_end,
|
|
1224
|
+
Fall(next_bb=normal_exit_target))
|
|
1225
|
+
self._record_handler_entry(try_region, handler, handler_entry)
|
|
1226
|
+
finally:
|
|
1227
|
+
self._region_stack.pop()
|
|
1228
|
+
|
|
1229
|
+
# Finally body (CFG-based path).
|
|
1230
|
+
if finally_async:
|
|
1231
|
+
assert finally_region is not None
|
|
1232
|
+
assert finally_entry_bb is not None
|
|
1233
|
+
assert finally_exit_bb is not None
|
|
1234
|
+
assert captured_exc_field is not None
|
|
1235
|
+
self._region_stack.append(finally_region)
|
|
1236
|
+
try:
|
|
1237
|
+
finally_end = self._build_block(
|
|
1238
|
+
finally_entry_bb, stmt.finally_body)
|
|
1239
|
+
if finally_end is not None:
|
|
1240
|
+
# Normal fall-through: route through finally_exit_bb
|
|
1241
|
+
# so AsyncFinallyExit (rethrow + deferred-return
|
|
1242
|
+
# replay) runs at the single replay site. A
|
|
1243
|
+
# return-in-finally inside the body also jumps to
|
|
1244
|
+
# finally_exit_bb (via the FinallyRegion's parking
|
|
1245
|
+
# info -- see gen_async._pending_return_info_for_region_stack).
|
|
1246
|
+
self._finish(finally_end, Fall(next_bb=finally_exit_bb))
|
|
1247
|
+
finally:
|
|
1248
|
+
self._region_stack.pop()
|
|
1249
|
+
# AsyncFinallyExit lives at the dedicated exit BB; fall to
|
|
1250
|
+
# join from there.
|
|
1251
|
+
self._blocks[finally_exit_bb].stmts.append(
|
|
1252
|
+
AsyncFinallyExit(
|
|
1253
|
+
captured_exc_field=captured_exc_field,
|
|
1254
|
+
pending_return_flag=pending_return_flag,
|
|
1255
|
+
pending_return_slot=pending_return_slot,
|
|
1256
|
+
))
|
|
1257
|
+
self._finish(finally_exit_bb, Fall(next_bb=join_bb))
|
|
1258
|
+
|
|
1259
|
+
return join_bb
|
|
1260
|
+
|
|
1261
|
+
def _record_handler_entry(self, region: TryRegion,
|
|
1262
|
+
handler: TpyExceptHandler,
|
|
1263
|
+
entry_bb: int) -> None:
|
|
1264
|
+
# Stash on a per-builder side map keyed by (id(region), id(handler))
|
|
1265
|
+
# so the emit can find handler entries without mutating the
|
|
1266
|
+
# frozen region dataclass.
|
|
1267
|
+
self._handler_entries[(id(region), id(handler))] = entry_bb
|
|
1268
|
+
|
|
1269
|
+
def get_handler_entry(self, region: TryRegion,
|
|
1270
|
+
handler: TpyExceptHandler) -> int | None:
|
|
1271
|
+
return self._handler_entries.get((id(region), id(handler)))
|
|
1272
|
+
|
|
1273
|
+
# -- with -----------------------------------------------------------
|
|
1274
|
+
|
|
1275
|
+
def _build_with(self, cur: int, stmt: TpyWith) -> int | None:
|
|
1276
|
+
"""Lower a sync `with X as t: body` whose body contains await
|
|
1277
|
+
into a CFG region. The context manager lives in a frame-hoisted
|
|
1278
|
+
field (`__with_ctx_<n>`); the case-emit wraps each case body in
|
|
1279
|
+
try/catch that runs `__exit__` on exception (with optional
|
|
1280
|
+
suppression -> transition to post-with state).
|
|
1281
|
+
|
|
1282
|
+
Async `with` (`stmt.is_async`) routes to a separate path: the
|
|
1283
|
+
`__aenter__` and `__aexit__` calls are themselves suspensions
|
|
1284
|
+
and need Yield BBs, modelled via a synthetic TryRegion whose
|
|
1285
|
+
CFG-based finally body holds the `__aexit__` yield. See
|
|
1286
|
+
`_build_async_with`."""
|
|
1287
|
+
if stmt.is_async:
|
|
1288
|
+
return self._build_async_with(cur, stmt)
|
|
1289
|
+
ctx_ns = self._with_uid_map.get(id(stmt))
|
|
1290
|
+
if ctx_ns is None:
|
|
1291
|
+
raise _CFGNotYetSupported(
|
|
1292
|
+
"with-stmt with await reached CFG builder without "
|
|
1293
|
+
"registered ctx uids (internal: pre-scan missed it).",
|
|
1294
|
+
loc=stmt.loc,
|
|
1295
|
+
)
|
|
1296
|
+
if len(ctx_ns) != len(stmt.items):
|
|
1297
|
+
raise _CFGNotYetSupported(
|
|
1298
|
+
"with-stmt ctx uid count mismatch (internal).",
|
|
1299
|
+
loc=stmt.loc,
|
|
1300
|
+
)
|
|
1301
|
+
post_with_bb = self._new_bb()
|
|
1302
|
+
# For each item, push WithRegion then emit WithEnter into
|
|
1303
|
+
# the current BB. Items are processed in source order
|
|
1304
|
+
# (outer-most CM enters first).
|
|
1305
|
+
regions: list[WithRegion] = []
|
|
1306
|
+
for item, ctx_n in zip(stmt.items, ctx_ns):
|
|
1307
|
+
region = WithRegion(
|
|
1308
|
+
item=item,
|
|
1309
|
+
ctx_n=ctx_n,
|
|
1310
|
+
post_with_bb=post_with_bb,
|
|
1311
|
+
loc_source=stmt,
|
|
1312
|
+
)
|
|
1313
|
+
self._blocks[cur].stmts.append(
|
|
1314
|
+
WithEnter(ctx_n=ctx_n, item=item))
|
|
1315
|
+
self._region_stack.append(region)
|
|
1316
|
+
regions.append(region)
|
|
1317
|
+
try:
|
|
1318
|
+
# Body BBs live inside all WithRegions.
|
|
1319
|
+
body_entry = self._new_bb()
|
|
1320
|
+
self._finish(cur, Fall(next_bb=body_entry))
|
|
1321
|
+
body_end = self._build_block(body_entry, stmt.body)
|
|
1322
|
+
if body_end is not None:
|
|
1323
|
+
# Fall out -> exit BB. Region exit (normal __exit__) is
|
|
1324
|
+
# emitted via `_emit_exit_region_finallies` when the
|
|
1325
|
+
# transition crosses out of the With regions.
|
|
1326
|
+
self._finish(body_end, Fall(next_bb=post_with_bb))
|
|
1327
|
+
finally:
|
|
1328
|
+
# Pop regions in reverse so the region_stack is clean
|
|
1329
|
+
# outside this with.
|
|
1330
|
+
for _ in regions:
|
|
1331
|
+
self._region_stack.pop()
|
|
1332
|
+
return post_with_bb
|
|
1333
|
+
|
|
1334
|
+
# -- async with -----------------------------------------------------
|
|
1335
|
+
|
|
1336
|
+
def _build_async_with(self, cur: int, stmt: TpyWith) -> int | None:
|
|
1337
|
+
"""Lower Python `async with X1 as a, X2 as b: body` (v1.5 M5).
|
|
1338
|
+
|
|
1339
|
+
Each item is decomposed left-to-right into:
|
|
1340
|
+
AsyncWithSetup -> Yield(__aenter__) -> [TryRegion {
|
|
1341
|
+
<recurse to next item, or body>
|
|
1342
|
+
} finally {
|
|
1343
|
+
Yield(__aexit__) ; AsyncFinallyExit
|
|
1344
|
+
}]
|
|
1345
|
+
|
|
1346
|
+
The TryRegion's finally body re-uses the M3.3 CFG-based-finally
|
|
1347
|
+
machinery: a `__finally_exc_<uid>` frame slot saves any in-flight
|
|
1348
|
+
exception via `std::current_exception()`; AsyncFinallyExit at
|
|
1349
|
+
the tail rethrows. `return` inside the body parks the value in
|
|
1350
|
+
`__finally_pending_<uid>` / `__finally_ret_<uid>` and walks
|
|
1351
|
+
through the same finally path (same as M3.3.2).
|
|
1352
|
+
|
|
1353
|
+
v1.5 M5 cleanup-only: `__aexit__` is called with all-None args
|
|
1354
|
+
regardless of whether an exception was caught. Inspecting
|
|
1355
|
+
`exc_val: Optional[BaseException]` is deferred to the
|
|
1356
|
+
polymorphic-exception-storage milestone (E9).
|
|
1357
|
+
"""
|
|
1358
|
+
ctx_ns = self._with_uid_map.get(id(stmt))
|
|
1359
|
+
if ctx_ns is None:
|
|
1360
|
+
raise _CFGNotYetSupported(
|
|
1361
|
+
"async-with reached CFG builder without registered "
|
|
1362
|
+
"ctx uids (internal: pre-scan missed it).",
|
|
1363
|
+
loc=stmt.loc,
|
|
1364
|
+
)
|
|
1365
|
+
if len(ctx_ns) != len(stmt.items):
|
|
1366
|
+
raise _CFGNotYetSupported(
|
|
1367
|
+
"async-with ctx uid count mismatch (internal).",
|
|
1368
|
+
loc=stmt.loc,
|
|
1369
|
+
)
|
|
1370
|
+
finally_uid = self._try_finally_uid_map.get(id(stmt))
|
|
1371
|
+
if finally_uid is None:
|
|
1372
|
+
raise _CFGNotYetSupported(
|
|
1373
|
+
"async-with reached CFG builder without registered "
|
|
1374
|
+
"finally-exc uid (internal: pre-scan missed it).",
|
|
1375
|
+
loc=stmt.loc,
|
|
1376
|
+
)
|
|
1377
|
+
# Multi-item `async with X as a, Y as b:`: would left-to-right
|
|
1378
|
+
# desugar to nested `async with` using the same finally_uid, so
|
|
1379
|
+
# the items would collide on the shared `__finally_exc_<n>` /
|
|
1380
|
+
# `__finally_pending_<n>` / `__finally_ret_<n>` fields. Reject
|
|
1381
|
+
# for now; users can nest two `async with` statements instead.
|
|
1382
|
+
if len(stmt.items) != 1:
|
|
1383
|
+
raise _CFGNotYetSupported(
|
|
1384
|
+
"multi-item `async with X as a, Y as b:` is not yet "
|
|
1385
|
+
"supported -- nest two `async with` statements instead",
|
|
1386
|
+
loc=stmt.loc,
|
|
1387
|
+
)
|
|
1388
|
+
# An `async with` synthesizes its own TryRegion with a CFG-based
|
|
1389
|
+
# finally body (the `__aexit__` Yield). Plain try/finally
|
|
1390
|
+
# nesting was lifted by extending AsyncFinallyExit to forward
|
|
1391
|
+
# parked state outward; the async-with case has not been lifted
|
|
1392
|
+
# because the synthesized finally also needs to keep its
|
|
1393
|
+
# __aexit__ call ordering correct across forwarding.
|
|
1394
|
+
for r in self._region_stack:
|
|
1395
|
+
if (isinstance(r, TryRegion)
|
|
1396
|
+
and r.captured_exc_field is not None):
|
|
1397
|
+
raise _CFGNotYetSupported(
|
|
1398
|
+
"`async with` nested inside another `await`-in-"
|
|
1399
|
+
"finally region is a planned follow-up.",
|
|
1400
|
+
loc=stmt.loc,
|
|
1401
|
+
)
|
|
1402
|
+
|
|
1403
|
+
item = stmt.items[0]
|
|
1404
|
+
ctx_n = ctx_ns[0]
|
|
1405
|
+
captured_exc_field = f"__finally_exc_{finally_uid}"
|
|
1406
|
+
|
|
1407
|
+
# 1. AsyncWithSetup populates the __with_ctx_<n> frame slot.
|
|
1408
|
+
self._blocks[cur].stmts.append(
|
|
1409
|
+
AsyncWithSetup(ctx_n=ctx_n, item=item))
|
|
1410
|
+
|
|
1411
|
+
# 2. Yield for `__aenter__()`. Synthesize a minimal TpyAwait so
|
|
1412
|
+
# the payload's dataclass invariants hold; emit branches on
|
|
1413
|
+
# `async_with_kind` and never reads the AST. The sub-coro
|
|
1414
|
+
# struct's C++ name is resolved at codegen time by looking up
|
|
1415
|
+
# `_async_with_struct_names[ctx_n]` (populated by the prescan).
|
|
1416
|
+
dummy_await = TpyAwait(value=item.context_expr, loc=stmt.loc)
|
|
1417
|
+
aenter_resume_bb = self._new_bb()
|
|
1418
|
+
aenter_payload = AwaitPayload(
|
|
1419
|
+
mode=AwaitMode.INLINE,
|
|
1420
|
+
sub_field_cpp_type="", # emit fills this from the CM type
|
|
1421
|
+
operand_expr=item.context_expr,
|
|
1422
|
+
kind=(AwaitKind.ASSIGN if item.target is not None
|
|
1423
|
+
else AwaitKind.DISCARD),
|
|
1424
|
+
bind_target=item.target,
|
|
1425
|
+
return_stmt=None,
|
|
1426
|
+
host_stmt=stmt,
|
|
1427
|
+
await_node=dummy_await,
|
|
1428
|
+
async_with_kind=AsyncWithKind.AENTER,
|
|
1429
|
+
async_with_ctx_n=ctx_n,
|
|
1430
|
+
)
|
|
1431
|
+
aenter_yield = Yield(
|
|
1432
|
+
payload=aenter_payload,
|
|
1433
|
+
resume_bb=aenter_resume_bb,
|
|
1434
|
+
suspension_index=len(self._yield_sites),
|
|
1435
|
+
)
|
|
1436
|
+
self._finish(cur, aenter_yield)
|
|
1437
|
+
self._yield_sites.append(aenter_yield)
|
|
1438
|
+
|
|
1439
|
+
# 3. Build TryRegion with CFG-based finally. The body is
|
|
1440
|
+
# stmt.body; the finally body holds the __aexit__ Yield +
|
|
1441
|
+
# AsyncFinallyExit.
|
|
1442
|
+
pending_return_flag = None
|
|
1443
|
+
pending_return_slot = None
|
|
1444
|
+
if _stmts_have_any_return(stmt.body):
|
|
1445
|
+
pending_return_flag = f"__finally_pending_{finally_uid}"
|
|
1446
|
+
if not self._func_returns_void:
|
|
1447
|
+
pending_return_slot = f"__finally_ret_{finally_uid}"
|
|
1448
|
+
|
|
1449
|
+
# Build finally body BB (with FinallyRegion on stack).
|
|
1450
|
+
finally_region = FinallyRegion(
|
|
1451
|
+
helper_name=captured_exc_field,
|
|
1452
|
+
loc_source=stmt,
|
|
1453
|
+
)
|
|
1454
|
+
self._region_stack.append(finally_region)
|
|
1455
|
+
finally_entry_bb = self._new_bb()
|
|
1456
|
+
self._region_stack.pop()
|
|
1457
|
+
|
|
1458
|
+
try_region = TryRegion(
|
|
1459
|
+
handlers=(),
|
|
1460
|
+
finally_helper_name=None,
|
|
1461
|
+
tier="throw",
|
|
1462
|
+
loc_source=stmt,
|
|
1463
|
+
finally_entry_bb=finally_entry_bb,
|
|
1464
|
+
captured_exc_field=captured_exc_field,
|
|
1465
|
+
pending_return_flag=pending_return_flag,
|
|
1466
|
+
pending_return_slot=pending_return_slot,
|
|
1467
|
+
)
|
|
1468
|
+
join_bb = self._new_bb()
|
|
1469
|
+
|
|
1470
|
+
# Try body lives inside TryRegion.
|
|
1471
|
+
self._region_stack.append(try_region)
|
|
1472
|
+
try:
|
|
1473
|
+
try_body_entry = self._new_bb()
|
|
1474
|
+
self._finish(aenter_resume_bb, Fall(next_bb=try_body_entry))
|
|
1475
|
+
body_end = self._build_block(try_body_entry, stmt.body)
|
|
1476
|
+
if body_end is not None:
|
|
1477
|
+
self._finish(body_end, Fall(next_bb=finally_entry_bb))
|
|
1478
|
+
finally:
|
|
1479
|
+
self._region_stack.pop()
|
|
1480
|
+
|
|
1481
|
+
# Finally body: Yield for __aexit__ + AsyncFinallyExit + Fall
|
|
1482
|
+
# to join.
|
|
1483
|
+
self._region_stack.append(finally_region)
|
|
1484
|
+
try:
|
|
1485
|
+
dummy_aexit_await = TpyAwait(
|
|
1486
|
+
value=item.context_expr, loc=stmt.loc)
|
|
1487
|
+
aexit_resume_bb = self._new_bb()
|
|
1488
|
+
aexit_payload = AwaitPayload(
|
|
1489
|
+
mode=AwaitMode.INLINE,
|
|
1490
|
+
sub_field_cpp_type="",
|
|
1491
|
+
operand_expr=item.context_expr,
|
|
1492
|
+
kind=AwaitKind.DISCARD,
|
|
1493
|
+
bind_target=None,
|
|
1494
|
+
return_stmt=None,
|
|
1495
|
+
host_stmt=stmt,
|
|
1496
|
+
await_node=dummy_aexit_await,
|
|
1497
|
+
async_with_kind=AsyncWithKind.AEXIT,
|
|
1498
|
+
async_with_ctx_n=ctx_n,
|
|
1499
|
+
)
|
|
1500
|
+
aexit_yield = Yield(
|
|
1501
|
+
payload=aexit_payload,
|
|
1502
|
+
resume_bb=aexit_resume_bb,
|
|
1503
|
+
suspension_index=len(self._yield_sites),
|
|
1504
|
+
)
|
|
1505
|
+
self._finish(finally_entry_bb, aexit_yield)
|
|
1506
|
+
self._yield_sites.append(aexit_yield)
|
|
1507
|
+
# After aexit resume: AsyncFinallyExit (rethrow saved exc if
|
|
1508
|
+
# any, deferred return if pending), then Fall to join.
|
|
1509
|
+
self._blocks[aexit_resume_bb].stmts.append(
|
|
1510
|
+
AsyncFinallyExit(
|
|
1511
|
+
captured_exc_field=captured_exc_field,
|
|
1512
|
+
pending_return_flag=pending_return_flag,
|
|
1513
|
+
pending_return_slot=pending_return_slot,
|
|
1514
|
+
))
|
|
1515
|
+
self._finish(aexit_resume_bb, Fall(next_bb=join_bb))
|
|
1516
|
+
finally:
|
|
1517
|
+
self._region_stack.pop()
|
|
1518
|
+
|
|
1519
|
+
return join_bb
|
|
1520
|
+
|
|
1521
|
+
# -- match -----------------------------------------------------------
|
|
1522
|
+
|
|
1523
|
+
def _build_match(self, cur: int, stmt: TpyMatch) -> int | None:
|
|
1524
|
+
"""Lower a `match` whose arm bodies contain a suspension. The
|
|
1525
|
+
dispatch stays a single suspension-free unit (emitted later by
|
|
1526
|
+
reusing `gen_match`); each arm body becomes its own BB chain
|
|
1527
|
+
(recursively built, so nested suspensions decompose), joining at
|
|
1528
|
+
`join_bb`. `match` introduces no region, so arm BBs and join
|
|
1529
|
+
share `cur`'s region stack -- no finally-chain delta on the
|
|
1530
|
+
dispatch->arm or arm->join transitions.
|
|
1531
|
+
|
|
1532
|
+
A guard or the subject expression cannot host a suspension (those
|
|
1533
|
+
appear only at statement position), so the dispatch is always
|
|
1534
|
+
suspension-free; only arm bodies suspend."""
|
|
1535
|
+
join_bb = self._new_bb()
|
|
1536
|
+
arm_bbs: list[int] = []
|
|
1537
|
+
all_terminate = True
|
|
1538
|
+
for case in stmt.cases:
|
|
1539
|
+
arm_bb = self._new_bb()
|
|
1540
|
+
arm_bbs.append(arm_bb)
|
|
1541
|
+
arm_end = self._build_block(arm_bb, case.body)
|
|
1542
|
+
if arm_end is not None:
|
|
1543
|
+
self._finish(arm_end, Fall(next_bb=join_bb))
|
|
1544
|
+
all_terminate = False
|
|
1545
|
+
# When the match is exhaustive AND every arm terminates, no
|
|
1546
|
+
# control path reaches past the match: the dispatch has no
|
|
1547
|
+
# fall-through and join is unreachable.
|
|
1548
|
+
reachable_join = None if (stmt.is_exhaustive and all_terminate) else join_bb
|
|
1549
|
+
self._finish(cur, MatchDispatch(
|
|
1550
|
+
match_stmt=stmt,
|
|
1551
|
+
arm_bbs=tuple(arm_bbs),
|
|
1552
|
+
join_bb=reachable_join,
|
|
1553
|
+
))
|
|
1554
|
+
return reachable_join
|
|
1555
|
+
|
|
1556
|
+
# -- post-construction passes ---------------------------------------
|
|
1557
|
+
|
|
1558
|
+
def _mark_resume_entries(self, cfg: CFG) -> None:
|
|
1559
|
+
for y in cfg.yield_sites:
|
|
1560
|
+
cfg.blocks[y.resume_bb].is_resume_entry = True
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
# -- Errors ---------------------------------------------------------------
|
|
1564
|
+
|
|
1565
|
+
class _CFGNotYetSupported(Exception):
|
|
1566
|
+
"""Raised when the CFG builder hits a shape that's known but not yet
|
|
1567
|
+
implemented. Caller catches and converts to a CodeGenError with the
|
|
1568
|
+
appropriate location.
|
|
1569
|
+
"""
|
|
1570
|
+
def __init__(self, msg: str, loc) -> None:
|
|
1571
|
+
super().__init__(msg)
|
|
1572
|
+
self.msg = msg
|
|
1573
|
+
self.loc = loc
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
# -- Helpers (top-level, used by gen_async too) ---------------------------
|
|
1577
|
+
|
|
1578
|
+
def _top_level_await_in(stmt: TpyStmt) -> TpyAwait | None:
|
|
1579
|
+
"""Return the TpyAwait if `stmt` is one of the top-level
|
|
1580
|
+
statement-position await shapes: assign with await RHS, vardecl
|
|
1581
|
+
with await init, return with await value, or expression-statement
|
|
1582
|
+
with bare await. Returns None otherwise.
|
|
1583
|
+
"""
|
|
1584
|
+
value: TpyExpr | None
|
|
1585
|
+
if isinstance(stmt, TpyAssign):
|
|
1586
|
+
value = stmt.value
|
|
1587
|
+
elif isinstance(stmt, TpyVarDecl):
|
|
1588
|
+
value = stmt.init
|
|
1589
|
+
elif isinstance(stmt, TpyReturn):
|
|
1590
|
+
value = stmt.value
|
|
1591
|
+
elif isinstance(stmt, TpyExprStmt):
|
|
1592
|
+
value = stmt.expr
|
|
1593
|
+
else:
|
|
1594
|
+
value = None
|
|
1595
|
+
if isinstance(value, TpyAwait):
|
|
1596
|
+
return value
|
|
1597
|
+
return None
|
|
1598
|
+
|
|
1599
|
+
|
|
1600
|
+
def _classify_await_position(
|
|
1601
|
+
stmt: TpyStmt, await_node: TpyAwait,
|
|
1602
|
+
) -> tuple[AwaitKind, str | None, TpyReturn | None]:
|
|
1603
|
+
"""Return (kind, bind_target, return_stmt) for a top-level await."""
|
|
1604
|
+
if isinstance(stmt, TpyAssign) and stmt.value is await_node:
|
|
1605
|
+
target = stmt.target
|
|
1606
|
+
if not isinstance(target, TpyName):
|
|
1607
|
+
raise _CFGNotYetSupported(
|
|
1608
|
+
"await result can only be bound to a simple name in v1; "
|
|
1609
|
+
"field/index targets are not yet supported.",
|
|
1610
|
+
loc=stmt.loc,
|
|
1611
|
+
)
|
|
1612
|
+
return (AwaitKind.ASSIGN, target.name, None)
|
|
1613
|
+
if isinstance(stmt, TpyVarDecl) and stmt.init is await_node:
|
|
1614
|
+
return (AwaitKind.VARDECL, stmt.name, None)
|
|
1615
|
+
if isinstance(stmt, TpyReturn) and stmt.value is await_node:
|
|
1616
|
+
return (AwaitKind.RETURN, None, stmt)
|
|
1617
|
+
if isinstance(stmt, TpyExprStmt) and stmt.expr is await_node:
|
|
1618
|
+
return (AwaitKind.DISCARD, None, None)
|
|
1619
|
+
raise _CFGNotYetSupported(
|
|
1620
|
+
"internal: _classify_await_position called on unsupported stmt.",
|
|
1621
|
+
loc=stmt.loc,
|
|
1622
|
+
)
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
|
|
1626
|
+
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
def _stmt_has_unbound_loop_transfer(stmt: TpyStmt) -> bool:
|
|
1630
|
+
"""True if `stmt` directly or transitively contains a break/continue
|
|
1631
|
+
that targets the enclosing loop (not bound by an inner loop in this
|
|
1632
|
+
statement). Used to decide whether a leaf compound (if/try/with)
|
|
1633
|
+
inside a CFG-decomposed loop needs decomposition so its break /
|
|
1634
|
+
continue translate to state transitions.
|
|
1635
|
+
|
|
1636
|
+
Stops at nested loops (while/for): their own break/continue bind
|
|
1637
|
+
to them and stay as C++ break/continue (the nested loop is a
|
|
1638
|
+
leaf compound)."""
|
|
1639
|
+
if isinstance(stmt, (TpyBreak, TpyContinue)):
|
|
1640
|
+
return True
|
|
1641
|
+
# Nested loop swallows its own break/continue.
|
|
1642
|
+
if isinstance(stmt, (TpyWhile, TpyForEach)):
|
|
1643
|
+
return False
|
|
1644
|
+
if hasattr(stmt, "sub_bodies"):
|
|
1645
|
+
for body in stmt.sub_bodies():
|
|
1646
|
+
for s in body:
|
|
1647
|
+
if _stmt_has_unbound_loop_transfer(s):
|
|
1648
|
+
return True
|
|
1649
|
+
return False
|
|
1650
|
+
|
|
1651
|
+
|