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,1997 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Match/Case Code Generation
|
|
3
|
+
|
|
4
|
+
Generates C++ code from TurboPython match/case statements.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from typing import TextIO, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from ..typesys import (
|
|
12
|
+
TpyType, NominalType, NoneType, OptionalType,
|
|
13
|
+
PendingStrType, UnionType, RecursiveAliasInstanceType,
|
|
14
|
+
LiteralType,
|
|
15
|
+
unwrap_readonly, is_any_str_type,
|
|
16
|
+
)
|
|
17
|
+
from .variant_access import VariantAccess
|
|
18
|
+
from ..parse import (
|
|
19
|
+
TpyStmt, TpyExpr, TpyFieldAccess, TpyName, TpyMatch, TpyMatchCase, TpyPattern,
|
|
20
|
+
TpySubscript, TpyWildcardPattern, TpyCapturePattern, TpyClassPattern,
|
|
21
|
+
TpyLiteralPattern, TpyValuePattern, TpyOrPattern, TpyAsPattern,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _match_subject_is_lvalue(expr: TpyExpr) -> bool:
|
|
26
|
+
"""A match subject is an lvalue when binding it with `auto&` is
|
|
27
|
+
safe (won't dangle) and useful (lets `case C() as v: v.f = ...`
|
|
28
|
+
write through to the original storage).
|
|
29
|
+
|
|
30
|
+
Plain names, field accesses whose target is itself an lvalue,
|
|
31
|
+
and subscripts on lvalue targets all qualify. Calls, literals,
|
|
32
|
+
and constructed temporaries do not."""
|
|
33
|
+
if isinstance(expr, TpyName):
|
|
34
|
+
return True
|
|
35
|
+
if isinstance(expr, TpyFieldAccess):
|
|
36
|
+
return _match_subject_is_lvalue(expr.obj)
|
|
37
|
+
if isinstance(expr, TpySubscript):
|
|
38
|
+
return _match_subject_is_lvalue(expr.obj)
|
|
39
|
+
return False
|
|
40
|
+
from .context import INDENT, CodeGenError, escape_cpp_name, escape_cpp_string, escape_cpp_char, cpp_string_literal_expr
|
|
41
|
+
from .string_dispatch import find_best_discriminator, STRING_SWITCH_THRESHOLD
|
|
42
|
+
from ..type_def_registry import is_fixed_int_type, is_bool_type, is_enum_type
|
|
43
|
+
from ..liveness import stmts_terminate
|
|
44
|
+
|
|
45
|
+
if TYPE_CHECKING:
|
|
46
|
+
from ..parse import SourceLocation
|
|
47
|
+
from .context import CodeGenContext
|
|
48
|
+
from .types import TypeResolver
|
|
49
|
+
from .expressions import ExpressionGenerator
|
|
50
|
+
from .statements import StatementGenerator
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class MatchGenerator:
|
|
54
|
+
"""Generates C++ code from TurboPython match/case statements."""
|
|
55
|
+
|
|
56
|
+
def __init__(self, ctx: CodeGenContext, types: TypeResolver,
|
|
57
|
+
expressions: ExpressionGenerator, stmts: StatementGenerator):
|
|
58
|
+
self.ctx = ctx
|
|
59
|
+
self.types = types
|
|
60
|
+
self.expressions = expressions
|
|
61
|
+
self.stmts = stmts
|
|
62
|
+
|
|
63
|
+
def gen_match(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
64
|
+
"""Generate a match/case statement. Uses switch when possible, if/elif otherwise."""
|
|
65
|
+
assert stmt.subject_type is not None
|
|
66
|
+
subject_type = unwrap_readonly(stmt.subject_type)
|
|
67
|
+
|
|
68
|
+
# @overload dead branch elimination for match on union subject
|
|
69
|
+
if (isinstance(subject_type, UnionType)
|
|
70
|
+
and isinstance(stmt.subject, TpyName)
|
|
71
|
+
and self.ctx.overload_param_types):
|
|
72
|
+
concrete = self.ctx.overload_param_types.get(stmt.subject.name)
|
|
73
|
+
if concrete is not None:
|
|
74
|
+
self._gen_match_overload_specialized(out, stmt, concrete, indent)
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
# Pre-declare variables first declared inside match arms
|
|
78
|
+
self.stmts._emit_branch_decls(out, stmt, indent)
|
|
79
|
+
|
|
80
|
+
# Evaluate subject and bind to a local
|
|
81
|
+
subject_code = self.expressions.gen_expr(stmt.subject)
|
|
82
|
+
self.ctx.temps.flush(out, indent)
|
|
83
|
+
# Use auto& for lvalue subjects (safe reference; lets a
|
|
84
|
+
# mutating `case C() as v: v.field = ...` arm write through
|
|
85
|
+
# to the original storage). Plain names are lvalues; so is a
|
|
86
|
+
# field access whose target is itself an lvalue (e.g.
|
|
87
|
+
# `self.payload`). Everything else (calls, temporaries) is
|
|
88
|
+
# copied as `auto` to avoid dangling references.
|
|
89
|
+
subject_is_lvalue = _match_subject_is_lvalue(stmt.subject)
|
|
90
|
+
binding = "auto&" if subject_is_lvalue else "auto"
|
|
91
|
+
# Record whether the subject is a stable frame-resident lvalue, for
|
|
92
|
+
# the resumable pointer-form binding emit: a pointer-into-subject arm
|
|
93
|
+
# binding survives a suspension only if the subject's storage outlives
|
|
94
|
+
# it. A non-lvalue subject is a dispatch-local copy; a *narrowed* name
|
|
95
|
+
# (union/isinstance) renders to a dispatch-local extraction alias
|
|
96
|
+
# (`__case_N`) even though it is syntactically a name -- so a nested
|
|
97
|
+
# `match` on a narrowed subject is NOT frame-stable. Both are unsafe
|
|
98
|
+
# for a pointer-form binding (the emit rejects that combination).
|
|
99
|
+
# Set unconditionally; read only on the resumable pointer-form path.
|
|
100
|
+
# Re-set per match (incl. nested) -- arm bindings emit before any
|
|
101
|
+
# nested-match arm body runs, so no save/restore is needed.
|
|
102
|
+
subject_is_narrowed = (isinstance(stmt.subject, TpyName)
|
|
103
|
+
and stmt.subject.name in self.ctx.narrowed_vars)
|
|
104
|
+
self.ctx.resumable_match_subject_is_lvalue = (
|
|
105
|
+
subject_is_lvalue and not subject_is_narrowed)
|
|
106
|
+
self.ctx.resumable_match_loc = stmt.loc
|
|
107
|
+
out.write(f"{indent}{binding} __match_subject = {subject_code};\n")
|
|
108
|
+
|
|
109
|
+
if isinstance(subject_type, (UnionType, RecursiveAliasInstanceType)):
|
|
110
|
+
# Generic recursive alias instances dispatch through the same
|
|
111
|
+
# variant-index path; _variant_index / VariantAccess read members
|
|
112
|
+
# via wrapper_info() / needs_wrapper(), both of which the instance
|
|
113
|
+
# implements.
|
|
114
|
+
has_guard = any(c.guard is not None for c in stmt.cases)
|
|
115
|
+
# Also use guarded path when union field guards cause multiple
|
|
116
|
+
# arms to share the same variant index
|
|
117
|
+
if not has_guard:
|
|
118
|
+
has_guard = self._has_shared_variant_index(stmt, subject_type)
|
|
119
|
+
if has_guard:
|
|
120
|
+
self._gen_match_guarded_union(out, stmt, subject_type, indent)
|
|
121
|
+
else:
|
|
122
|
+
self._gen_match_switch_union(out, stmt, subject_type, indent)
|
|
123
|
+
elif is_enum_type(subject_type):
|
|
124
|
+
self._gen_match_switch_enum(out, stmt, indent)
|
|
125
|
+
elif isinstance(subject_type, LiteralType):
|
|
126
|
+
base = subject_type.base_type
|
|
127
|
+
if is_any_str_type(base):
|
|
128
|
+
if self._should_switch_str(stmt):
|
|
129
|
+
self._gen_match_switch_str(out, stmt, indent)
|
|
130
|
+
else:
|
|
131
|
+
self._gen_match_if_elif(out, stmt, indent)
|
|
132
|
+
elif is_fixed_int_type(base):
|
|
133
|
+
self._gen_match_switch_primitive(out, stmt, indent)
|
|
134
|
+
else:
|
|
135
|
+
# bool (and anything else literal-like) falls through to if/elif:
|
|
136
|
+
# `switch(bool_var)` is valid C++ but trips -Wswitch-bool, and an
|
|
137
|
+
# if-chain is the same shape with two arms anyway.
|
|
138
|
+
self._gen_match_if_elif(out, stmt, indent)
|
|
139
|
+
elif is_fixed_int_type(subject_type):
|
|
140
|
+
self._gen_match_switch_primitive(out, stmt, indent)
|
|
141
|
+
elif is_bool_type(subject_type):
|
|
142
|
+
self._gen_match_if_elif(out, stmt, indent)
|
|
143
|
+
elif isinstance(subject_type, NominalType) and subject_type.is_user_record:
|
|
144
|
+
has_guard = any(c.guard is not None for c in stmt.cases)
|
|
145
|
+
if has_guard:
|
|
146
|
+
self._gen_match_guarded_record(out, stmt, indent)
|
|
147
|
+
else:
|
|
148
|
+
self._gen_match_if_elif_record(out, stmt, indent)
|
|
149
|
+
elif isinstance(subject_type, OptionalType):
|
|
150
|
+
partition = self._partition_optional_cases(stmt.cases)
|
|
151
|
+
if partition is not None:
|
|
152
|
+
none_cases, inner_cases = partition
|
|
153
|
+
self._gen_match_optimized_optional(
|
|
154
|
+
out, stmt, subject_type, none_cases, inner_cases, indent,
|
|
155
|
+
)
|
|
156
|
+
else:
|
|
157
|
+
self._gen_match_if_elif_optional(out, stmt, subject_type, indent)
|
|
158
|
+
elif is_any_str_type(subject_type):
|
|
159
|
+
if self._should_switch_str(stmt):
|
|
160
|
+
self._gen_match_switch_str(out, stmt, indent)
|
|
161
|
+
else:
|
|
162
|
+
self._gen_match_if_elif(out, stmt, indent)
|
|
163
|
+
else:
|
|
164
|
+
self._gen_match_if_elif(out, stmt, indent)
|
|
165
|
+
|
|
166
|
+
# If the match is exhaustive (sema-proven) AND every arm body
|
|
167
|
+
# terminates, the post-match control point is unreachable. Tell the
|
|
168
|
+
# compiler so -- otherwise it warns "control reaches end of non-void
|
|
169
|
+
# function" when the match is the function's last statement.
|
|
170
|
+
#
|
|
171
|
+
# For non-exhaustive matches, falling through the end-label is the
|
|
172
|
+
# user's intent (sema only warns), so emitting std::unreachable()
|
|
173
|
+
# there would let the optimizer eliminate code that the user expects
|
|
174
|
+
# to execute.
|
|
175
|
+
if (stmt.is_exhaustive
|
|
176
|
+
and stmt.cases
|
|
177
|
+
and all(stmts_terminate(c.body) for c in stmt.cases)):
|
|
178
|
+
out.write(f"{indent}::std::unreachable();\n")
|
|
179
|
+
|
|
180
|
+
def _gen_match_overload_specialized(
|
|
181
|
+
self, out: TextIO, stmt: TpyMatch, concrete_type: 'TpyType', indent: str,
|
|
182
|
+
) -> None:
|
|
183
|
+
"""Emit only the matching arm for a match on a concrete overload param.
|
|
184
|
+
|
|
185
|
+
The subject variable has a concrete (non-variant) type in this overload,
|
|
186
|
+
so we find the arm whose class pattern matches and emit its body directly.
|
|
187
|
+
"""
|
|
188
|
+
assert isinstance(stmt.subject, TpyName)
|
|
189
|
+
subject_name = stmt.subject.name
|
|
190
|
+
|
|
191
|
+
# This @overload-specialization path emits the matched arm body
|
|
192
|
+
# directly via `gen_stmt`, bypassing `_emit_case_body` -- so the
|
|
193
|
+
# resumable-frame arm-routing hook would never fire and a suspension
|
|
194
|
+
# in the arm would be emitted as straight-line code (dropped). Refuse
|
|
195
|
+
# rather than silently miscompile; the resumable `match` decomposition
|
|
196
|
+
# does not cover the overload-specialized subject.
|
|
197
|
+
if self.ctx.resumable_arm_emitter is not None:
|
|
198
|
+
raise CodeGenError(
|
|
199
|
+
"a `yield`/`await` inside a `match` on an @overload-"
|
|
200
|
+
"specialized parameter is not yet supported",
|
|
201
|
+
loc=stmt.loc,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def emit_field_bindings(pattern: TpyClassPattern) -> None:
|
|
205
|
+
self._gen_match_field_bindings(out, pattern, subject_name, indent)
|
|
206
|
+
|
|
207
|
+
for case in stmt.cases:
|
|
208
|
+
pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
|
|
209
|
+
|
|
210
|
+
if isinstance(pattern, TpyClassPattern) and pattern.resolved_type is not None:
|
|
211
|
+
if pattern.resolved_type == concrete_type:
|
|
212
|
+
emit_field_bindings(pattern)
|
|
213
|
+
if as_name:
|
|
214
|
+
out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
|
|
215
|
+
for s in case.body:
|
|
216
|
+
self.stmts.gen_stmt(out, s)
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
220
|
+
for alt in pattern.patterns:
|
|
221
|
+
if isinstance(alt, TpyClassPattern) and alt.resolved_type == concrete_type:
|
|
222
|
+
emit_field_bindings(alt)
|
|
223
|
+
if as_name:
|
|
224
|
+
out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
|
|
225
|
+
for s in case.body:
|
|
226
|
+
self.stmts.gen_stmt(out, s)
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
230
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
231
|
+
out.write(f"{indent}auto& {escape_cpp_name(pattern.name)} = {subject_name};\n")
|
|
232
|
+
if as_name:
|
|
233
|
+
out.write(f"{indent}auto& {escape_cpp_name(as_name)} = {subject_name};\n")
|
|
234
|
+
for s in case.body:
|
|
235
|
+
self.stmts.gen_stmt(out, s)
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
raise AssertionError(
|
|
239
|
+
f"No match arm found for concrete type {concrete_type} "
|
|
240
|
+
f"in @overload specialization"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def _subject_is_ptr_variant(
|
|
244
|
+
self, subject: TpyExpr, subject_type: TpyType,
|
|
245
|
+
) -> bool:
|
|
246
|
+
"""Whether the match subject's storage is pointer-variant.
|
|
247
|
+
|
|
248
|
+
``is_ptr_variant_union(subject_type)`` reflects the type's repr in
|
|
249
|
+
its primary contexts (params, locals, returns) but the same union
|
|
250
|
+
is stored value-variant when it's a record field or container
|
|
251
|
+
element. The match ``get_expr`` ('*std::get' vs 'std::get') has to
|
|
252
|
+
match actual storage, not just type. Falls back to the assignment
|
|
253
|
+
path's classifier so the same rules apply on both sides.
|
|
254
|
+
"""
|
|
255
|
+
if not self.ctx.is_ptr_variant_union(subject_type):
|
|
256
|
+
return False
|
|
257
|
+
return self.ctx.is_ptr_variant_source(subject)
|
|
258
|
+
|
|
259
|
+
def _gen_match_switch_union(
|
|
260
|
+
self, out: TextIO, stmt: TpyMatch, subject_type: UnionType, indent: str,
|
|
261
|
+
) -> None:
|
|
262
|
+
"""Generate switch (__match_subject.index()) for union subjects."""
|
|
263
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
264
|
+
is_ptr_var = self._subject_is_ptr_variant(stmt.subject, subject_type)
|
|
265
|
+
va = VariantAccess("__match_subject", subject_type, is_ptr_variant=is_ptr_var)
|
|
266
|
+
out.write(f"{indent}switch ({va.index_expr()}) {{\n")
|
|
267
|
+
|
|
268
|
+
for i, case in enumerate(stmt.cases):
|
|
269
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
270
|
+
pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
|
|
271
|
+
|
|
272
|
+
if isinstance(pattern, TpyClassPattern):
|
|
273
|
+
assert pattern.resolved_type is not None
|
|
274
|
+
idx = self._variant_index(subject_type, pattern.resolved_type)
|
|
275
|
+
out.write(f"{indent}case {idx}: {{\n")
|
|
276
|
+
case_var: str | None = None
|
|
277
|
+
if pattern.keywords or case.type_facts:
|
|
278
|
+
case_var = f"__case_{i}"
|
|
279
|
+
out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
|
|
280
|
+
if pattern.keywords:
|
|
281
|
+
self._gen_match_field_bindings(out, pattern, case_var, inner)
|
|
282
|
+
self._emit_binding(out, as_name, as_raw_name,
|
|
283
|
+
case_var or va.get_by_index(idx), inner)
|
|
284
|
+
# Narrowing
|
|
285
|
+
saved_narrow: dict[str, str | None] = {}
|
|
286
|
+
if case.type_facts:
|
|
287
|
+
for var_name in case.type_facts:
|
|
288
|
+
saved_narrow[var_name] = self.ctx.narrowed_vars.get(var_name)
|
|
289
|
+
self.ctx.narrowed_vars[var_name] = case_var
|
|
290
|
+
self.ctx.indent_level += 1
|
|
291
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
292
|
+
self.ctx.indent_level -= 1
|
|
293
|
+
self.stmts.ctx.restore_narrowed_vars(saved_narrow)
|
|
294
|
+
out.write(f"{inner}break;\n")
|
|
295
|
+
out.write(f"{indent}}}\n")
|
|
296
|
+
|
|
297
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
298
|
+
has_bindings = any(
|
|
299
|
+
isinstance(alt, TpyClassPattern) and alt.keywords
|
|
300
|
+
for alt in pattern.patterns
|
|
301
|
+
)
|
|
302
|
+
has_wildcard = any(
|
|
303
|
+
isinstance(alt, (TpyWildcardPattern, TpyCapturePattern))
|
|
304
|
+
for alt in pattern.patterns
|
|
305
|
+
)
|
|
306
|
+
if has_wildcard:
|
|
307
|
+
# Wildcard subsumes all alternatives -> default
|
|
308
|
+
out.write(f"{indent}default: {{\n")
|
|
309
|
+
self.ctx.indent_level += 1
|
|
310
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
311
|
+
self.ctx.indent_level -= 1
|
|
312
|
+
out.write(f"{inner}break;\n")
|
|
313
|
+
out.write(f"{indent}}}\n")
|
|
314
|
+
elif not has_bindings:
|
|
315
|
+
# No bindings: case fallthrough
|
|
316
|
+
for alt in pattern.patterns:
|
|
317
|
+
assert isinstance(alt, TpyClassPattern) and alt.resolved_type is not None
|
|
318
|
+
idx = self._variant_index(subject_type, alt.resolved_type)
|
|
319
|
+
out.write(f"{indent}case {idx}:\n")
|
|
320
|
+
out.write(f"{indent}{{\n")
|
|
321
|
+
self.ctx.indent_level += 1
|
|
322
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
323
|
+
self.ctx.indent_level -= 1
|
|
324
|
+
out.write(f"{inner}break;\n")
|
|
325
|
+
out.write(f"{indent}}}\n")
|
|
326
|
+
else:
|
|
327
|
+
# With bindings: body duplication per alternative
|
|
328
|
+
for j, alt in enumerate(pattern.patterns):
|
|
329
|
+
assert isinstance(alt, TpyClassPattern) and alt.resolved_type is not None
|
|
330
|
+
idx = self._variant_index(subject_type, alt.resolved_type)
|
|
331
|
+
out.write(f"{indent}case {idx}: {{\n")
|
|
332
|
+
case_var = f"__case_{i}_{j}"
|
|
333
|
+
out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
|
|
334
|
+
if alt.keywords:
|
|
335
|
+
self._gen_match_field_bindings(out, alt, case_var, inner)
|
|
336
|
+
saved = self._apply_narrowing(case.type_facts, case_var)
|
|
337
|
+
self.ctx.indent_level += 1
|
|
338
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
339
|
+
self.ctx.indent_level -= 1
|
|
340
|
+
self.stmts.ctx.restore_narrowed_vars(saved)
|
|
341
|
+
out.write(f"{inner}break;\n")
|
|
342
|
+
out.write(f"{indent}}}\n")
|
|
343
|
+
|
|
344
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
345
|
+
self._gen_switch_default_arm(
|
|
346
|
+
out, pattern, as_name, as_raw_name, case.body,
|
|
347
|
+
indent, inner, case.type_facts)
|
|
348
|
+
|
|
349
|
+
elif isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
|
|
350
|
+
idx = self._variant_index(subject_type, NoneType())
|
|
351
|
+
out.write(f"{indent}case {idx}: {{\n")
|
|
352
|
+
self.ctx.indent_level += 1
|
|
353
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
354
|
+
self.ctx.indent_level -= 1
|
|
355
|
+
out.write(f"{inner}break;\n")
|
|
356
|
+
out.write(f"{indent}}}\n")
|
|
357
|
+
|
|
358
|
+
else:
|
|
359
|
+
raise CodeGenError(f"Unsupported pattern in union switch: {type(pattern).__name__}")
|
|
360
|
+
|
|
361
|
+
out.write(f"{indent}}}\n")
|
|
362
|
+
|
|
363
|
+
def _gen_match_switch_enum(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
364
|
+
"""Generate switch (__match_subject) for enum subjects."""
|
|
365
|
+
groups = self._group_switch_arms(stmt, kind="enum")
|
|
366
|
+
self._emit_switch_groups(out, groups, indent, is_exhaustive=stmt.is_exhaustive)
|
|
367
|
+
|
|
368
|
+
def _gen_match_switch_primitive(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
369
|
+
"""Generate switch (__match_subject) for int/bool subjects."""
|
|
370
|
+
groups = self._group_switch_arms(stmt, kind="primitive")
|
|
371
|
+
self._emit_switch_groups(out, groups, indent, is_exhaustive=stmt.is_exhaustive)
|
|
372
|
+
|
|
373
|
+
# Entry in a switch arm group:
|
|
374
|
+
# (guard, body, capture_escaped, as_escaped, raw_names, loc, type_facts)
|
|
375
|
+
# raw_names: set of raw Python names for declared_vars lookup
|
|
376
|
+
_SwitchEntry = tuple[
|
|
377
|
+
TpyExpr | None, list['TpyStmt'], str | None, str | None, set[str],
|
|
378
|
+
'SourceLocation | None', dict[str, TpyType],
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
def _group_switch_arms(
|
|
382
|
+
self, stmt_or_cases: 'TpyMatch | list[TpyMatchCase]', kind: str,
|
|
383
|
+
) -> list[tuple[list[str], list[_SwitchEntry]]]:
|
|
384
|
+
"""Group match cases by switch label for enum/primitive subjects.
|
|
385
|
+
|
|
386
|
+
Returns a list of (labels, entries) where:
|
|
387
|
+
- labels: list of case label strings, or ["default"] for wildcard
|
|
388
|
+
- entries: list of (guard, body, cap_escaped, as_escaped, raw_names, loc)
|
|
389
|
+
Same-value cases with guards are merged into a single group.
|
|
390
|
+
Entries are ordered guarded-first, unguarded-last (enforced by sema
|
|
391
|
+
duplicate-case check which only allows same-value repeats with guards).
|
|
392
|
+
"""
|
|
393
|
+
cases: list[TpyMatchCase] = (
|
|
394
|
+
stmt_or_cases if isinstance(stmt_or_cases, list) else stmt_or_cases.cases
|
|
395
|
+
)
|
|
396
|
+
groups: dict[str, tuple[list[str], list[MatchGenerator._SwitchEntry]]] = {}
|
|
397
|
+
default_entries: list[MatchGenerator._SwitchEntry] = []
|
|
398
|
+
|
|
399
|
+
for case in cases:
|
|
400
|
+
pattern, as_escaped, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
401
|
+
raw_names: set[str] = set()
|
|
402
|
+
if as_raw is not None:
|
|
403
|
+
raw_names.add(as_raw)
|
|
404
|
+
|
|
405
|
+
if isinstance(pattern, (TpyValuePattern, TpyLiteralPattern)):
|
|
406
|
+
if kind == "enum":
|
|
407
|
+
assert isinstance(pattern, TpyValuePattern)
|
|
408
|
+
label = self.expressions.gen_expr(pattern.expr)
|
|
409
|
+
else:
|
|
410
|
+
assert isinstance(pattern, TpyLiteralPattern)
|
|
411
|
+
label = self._switch_literal_label(pattern)
|
|
412
|
+
entry: MatchGenerator._SwitchEntry = (
|
|
413
|
+
case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts,
|
|
414
|
+
)
|
|
415
|
+
if label in groups:
|
|
416
|
+
groups[label][1].append(entry)
|
|
417
|
+
else:
|
|
418
|
+
groups[label] = ([label], [entry])
|
|
419
|
+
|
|
420
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
421
|
+
has_wild = any(
|
|
422
|
+
isinstance(alt, (TpyWildcardPattern, TpyCapturePattern))
|
|
423
|
+
for alt in pattern.patterns
|
|
424
|
+
)
|
|
425
|
+
if has_wild:
|
|
426
|
+
default_entries.append((case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts))
|
|
427
|
+
else:
|
|
428
|
+
labels = []
|
|
429
|
+
for alt in pattern.patterns:
|
|
430
|
+
if kind == "enum":
|
|
431
|
+
assert isinstance(alt, TpyValuePattern)
|
|
432
|
+
labels.append(self.expressions.gen_expr(alt.expr))
|
|
433
|
+
else:
|
|
434
|
+
assert isinstance(alt, TpyLiteralPattern)
|
|
435
|
+
labels.append(self._switch_literal_label(alt))
|
|
436
|
+
key = "|".join(labels)
|
|
437
|
+
entry = (case.guard, case.body, None, as_escaped, raw_names, case.loc, case.type_facts)
|
|
438
|
+
if key in groups:
|
|
439
|
+
groups[key][1].append(entry)
|
|
440
|
+
else:
|
|
441
|
+
groups[key] = (labels, [entry])
|
|
442
|
+
|
|
443
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
444
|
+
cap_escaped = escape_cpp_name(pattern.name) if isinstance(pattern, TpyCapturePattern) else None
|
|
445
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
446
|
+
raw_names.add(pattern.name)
|
|
447
|
+
default_entries.append((case.guard, case.body, cap_escaped, as_escaped, raw_names, case.loc, case.type_facts))
|
|
448
|
+
|
|
449
|
+
else:
|
|
450
|
+
raise CodeGenError(f"Unsupported pattern in {kind} switch: {type(pattern).__name__}")
|
|
451
|
+
|
|
452
|
+
result = list(groups.values())
|
|
453
|
+
if default_entries:
|
|
454
|
+
result.append((["default"], default_entries))
|
|
455
|
+
return result
|
|
456
|
+
|
|
457
|
+
def _emit_switch_groups(
|
|
458
|
+
self, out: TextIO,
|
|
459
|
+
groups: list[tuple[list[str], list[_SwitchEntry]]],
|
|
460
|
+
indent: str,
|
|
461
|
+
subject_expr: str = "__match_subject",
|
|
462
|
+
is_exhaustive: bool = False,
|
|
463
|
+
) -> None:
|
|
464
|
+
"""Emit a switch statement from grouped arms.
|
|
465
|
+
|
|
466
|
+
is_exhaustive: caller's promise that the match covers every possible
|
|
467
|
+
subject value. When true, the synthetic ``default: break;`` is omitted
|
|
468
|
+
so future enum members added upstream still trigger ``-Wswitch``.
|
|
469
|
+
"""
|
|
470
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
471
|
+
|
|
472
|
+
# Check if any non-default group needs guard fallback to default
|
|
473
|
+
has_default = any(labels == ["default"] for labels, _ in groups)
|
|
474
|
+
needs_default_goto = has_default and any(
|
|
475
|
+
labels != ["default"]
|
|
476
|
+
and all(g is not None for g, _, _, _, _, _, _ in entries)
|
|
477
|
+
for labels, entries in groups
|
|
478
|
+
)
|
|
479
|
+
default_label: str | None = None
|
|
480
|
+
if needs_default_goto:
|
|
481
|
+
self.ctx.match_counter += 1
|
|
482
|
+
default_label = f"__match_default_{self.ctx.match_counter}"
|
|
483
|
+
|
|
484
|
+
out.write(f"{indent}switch ({subject_expr}) {{\n")
|
|
485
|
+
|
|
486
|
+
for labels, entries in groups:
|
|
487
|
+
# Emit source comment for first entry in group
|
|
488
|
+
if entries:
|
|
489
|
+
self.ctx.emit_source_comment(out, entries[0][5], indent)
|
|
490
|
+
# Emit case labels
|
|
491
|
+
if labels == ["default"]:
|
|
492
|
+
if default_label is not None:
|
|
493
|
+
out.write(f"{indent}default: {default_label}: {{\n")
|
|
494
|
+
else:
|
|
495
|
+
out.write(f"{indent}default: {{\n")
|
|
496
|
+
elif len(labels) == 1:
|
|
497
|
+
out.write(f"{indent}case {labels[0]}: {{\n")
|
|
498
|
+
else:
|
|
499
|
+
for label in labels:
|
|
500
|
+
out.write(f"{indent}case {label}:\n")
|
|
501
|
+
out.write(f"{indent}{{\n")
|
|
502
|
+
|
|
503
|
+
# Bound literal_facts/protocol_narrowings to the case scope.
|
|
504
|
+
# type_facts from the first entry apply to the whole group (all
|
|
505
|
+
# entries in a group match the same value, so facts are equivalent).
|
|
506
|
+
type_facts_0 = entries[0][6]
|
|
507
|
+
|
|
508
|
+
# Single entry, no guard -> simple body
|
|
509
|
+
if len(entries) == 1 and entries[0][0] is None:
|
|
510
|
+
_, body, cap, as_name, raw_names, _loc, _tf = entries[0]
|
|
511
|
+
self._emit_switch_binding(out, cap, as_name, raw_names, inner, subject_expr)
|
|
512
|
+
self.ctx.indent_level += 1
|
|
513
|
+
self._emit_case_body(out, body, type_facts_0)
|
|
514
|
+
self.ctx.indent_level -= 1
|
|
515
|
+
else:
|
|
516
|
+
# Emit capture/as binding before the guard chain so guards
|
|
517
|
+
# can reference the bound variable
|
|
518
|
+
bindings_emitted: set[str] = set()
|
|
519
|
+
for _g, _b, cap, as_name, raw_names, _loc, _tf in entries:
|
|
520
|
+
for escaped, raw in self._binding_pairs(cap, as_name, raw_names):
|
|
521
|
+
if escaped not in bindings_emitted:
|
|
522
|
+
self._emit_binding(out, escaped, raw, subject_expr, inner)
|
|
523
|
+
bindings_emitted.add(escaped)
|
|
524
|
+
|
|
525
|
+
# Guard chain: if (g1) { body1 } else if (g2) { body2 } else { fallback }
|
|
526
|
+
has_unguarded = any(g is None for g, _, _, _, _, _, _ in entries)
|
|
527
|
+
if_opened = False
|
|
528
|
+
for _j, (guard, body, _cap, _as, _raw, _loc, _tf) in enumerate(entries):
|
|
529
|
+
if guard is not None:
|
|
530
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
531
|
+
self.ctx.temps.flush(out, inner)
|
|
532
|
+
keyword = "if" if not if_opened else "} else if"
|
|
533
|
+
if_opened = True
|
|
534
|
+
out.write(f"{inner}{keyword} ({guard_code}) {{\n")
|
|
535
|
+
self.ctx.indent_level += 2
|
|
536
|
+
self._emit_case_body(out, body, type_facts_0)
|
|
537
|
+
self.ctx.indent_level -= 2
|
|
538
|
+
else:
|
|
539
|
+
# Unguarded entry: final else
|
|
540
|
+
out.write(f"{inner}}} else {{\n")
|
|
541
|
+
self.ctx.indent_level += 2
|
|
542
|
+
self._emit_case_body(out, body, type_facts_0)
|
|
543
|
+
self.ctx.indent_level -= 2
|
|
544
|
+
# Close last if/else and add goto fallback if needed
|
|
545
|
+
if has_unguarded:
|
|
546
|
+
out.write(f"{inner}}}\n")
|
|
547
|
+
elif default_label is not None and labels != ["default"]:
|
|
548
|
+
out.write(f"{inner}}}\n")
|
|
549
|
+
out.write(f"{inner}goto {default_label};\n")
|
|
550
|
+
else:
|
|
551
|
+
out.write(f"{inner}}}\n")
|
|
552
|
+
|
|
553
|
+
out.write(f"{inner}break;\n")
|
|
554
|
+
out.write(f"{indent}}}\n")
|
|
555
|
+
|
|
556
|
+
# Synthetic default for non-exhaustive switches. -Wswitch (in -Wall)
|
|
557
|
+
# flags an enum switch that doesn't list every enum value; non-
|
|
558
|
+
# exhaustive matches on int/bool don't trigger it but adding a
|
|
559
|
+
# default is harmless. Skipped when the user already provided a
|
|
560
|
+
# wildcard arm (`has_default`) or when sema proved the match
|
|
561
|
+
# exhaustive (so future enum members added upstream still trigger
|
|
562
|
+
# -Wswitch instead of being silently swallowed).
|
|
563
|
+
if not has_default and not is_exhaustive:
|
|
564
|
+
out.write(f"{indent}default: break;\n")
|
|
565
|
+
|
|
566
|
+
out.write(f"{indent}}}\n")
|
|
567
|
+
|
|
568
|
+
def _emit_binding(
|
|
569
|
+
self, out: TextIO, escaped: str | None, raw: str | None,
|
|
570
|
+
subject_expr: str, indent: str,
|
|
571
|
+
) -> None:
|
|
572
|
+
"""Emit a variable binding if name is not None.
|
|
573
|
+
|
|
574
|
+
Uses assignment if pre-declared (leaked from match arm), auto& otherwise.
|
|
575
|
+
"""
|
|
576
|
+
if escaped is None:
|
|
577
|
+
return
|
|
578
|
+
raw_name = raw or escaped
|
|
579
|
+
# Resumable frame (H1): a bound name that is a frame field must be
|
|
580
|
+
# WRITTEN to the field, not re-declared as a shadowing C++ local --
|
|
581
|
+
# in a decomposed (suspending) match the arm body is a separate
|
|
582
|
+
# state that reads the live field, so an `auto&` dispatch-local
|
|
583
|
+
# would be lost at the state split. The value/frame_slot/pointer
|
|
584
|
+
# split mirrors the field-write logic in `_gen_assign`.
|
|
585
|
+
if (self.ctx.in_generator_body
|
|
586
|
+
and raw_name in self.ctx.generator_field_names):
|
|
587
|
+
if raw_name in self.ctx.pointer_locals:
|
|
588
|
+
# Pointer-repr `Optional` field: bridge storage form
|
|
589
|
+
# (`std::optional<T>`) to the borrow-form `T*` frame slot via
|
|
590
|
+
# `optional_to_ptr` (raw `&` would take the address of the
|
|
591
|
+
# optional -- wrong type). The pointer aliases into the
|
|
592
|
+
# subject, so a non-lvalue subject (dispatch-local copy) would
|
|
593
|
+
# dangle across the suspension: reject rather than miscompile.
|
|
594
|
+
if not self.ctx.resumable_match_subject_is_lvalue:
|
|
595
|
+
raise CodeGenError(
|
|
596
|
+
"binding a `T | None` field from a non-lvalue "
|
|
597
|
+
"`match` subject inside a generator/`async` is not "
|
|
598
|
+
"yet supported (the binding would dangle across a "
|
|
599
|
+
"suspension); bind the subject to a local first",
|
|
600
|
+
loc=self.ctx.resumable_match_loc)
|
|
601
|
+
out.write(
|
|
602
|
+
f"{indent}{escaped} = "
|
|
603
|
+
f"::tpy::optional_to_ptr({subject_expr});\n")
|
|
604
|
+
elif raw_name in self.ctx.generator_frame_slot_locals:
|
|
605
|
+
out.write(f"{indent}{escaped}.emplace({subject_expr});\n")
|
|
606
|
+
else:
|
|
607
|
+
out.write(f"{indent}{escaped} = {subject_expr};\n")
|
|
608
|
+
return
|
|
609
|
+
if raw_name in self.ctx.declared_vars:
|
|
610
|
+
out.write(f"{indent}{escaped} = {subject_expr};\n")
|
|
611
|
+
else:
|
|
612
|
+
out.write(f"{indent}auto& {escaped} = {subject_expr};\n")
|
|
613
|
+
|
|
614
|
+
def _emit_switch_binding(
|
|
615
|
+
self, out: TextIO, cap: str | None, as_name: str | None,
|
|
616
|
+
raw_names: set[str], inner: str,
|
|
617
|
+
subject_expr: str = "__match_subject",
|
|
618
|
+
) -> None:
|
|
619
|
+
"""Emit capture/as binding in a switch arm, respecting declared_vars."""
|
|
620
|
+
for escaped, raw in self._binding_pairs(cap, as_name, raw_names):
|
|
621
|
+
self._emit_binding(out, escaped, raw, subject_expr, inner)
|
|
622
|
+
|
|
623
|
+
@staticmethod
|
|
624
|
+
def _binding_pairs(
|
|
625
|
+
cap: str | None, as_name: str | None, raw_names: set[str],
|
|
626
|
+
) -> list[tuple[str, str]]:
|
|
627
|
+
"""Return (escaped_name, raw_name) pairs for binding emission."""
|
|
628
|
+
pairs: list[tuple[str, str]] = []
|
|
629
|
+
# raw_names may contain 1 or 2 entries; match escaped names to raw
|
|
630
|
+
raw_list = list(raw_names)
|
|
631
|
+
if cap is not None:
|
|
632
|
+
raw = next((r for r in raw_list if escape_cpp_name(r) == cap), cap)
|
|
633
|
+
pairs.append((cap, raw))
|
|
634
|
+
if as_name is not None and as_name != cap:
|
|
635
|
+
raw = next((r for r in raw_list if escape_cpp_name(r) == as_name), as_name)
|
|
636
|
+
pairs.append((as_name, raw))
|
|
637
|
+
return pairs
|
|
638
|
+
|
|
639
|
+
def _unwrap_as_pattern(
|
|
640
|
+
self, pattern: TpyPattern,
|
|
641
|
+
) -> tuple[TpyPattern, str | None, str | None]:
|
|
642
|
+
"""Unwrap as-pattern, returning (inner_pattern, escaped_as_name, raw_as_name)."""
|
|
643
|
+
if isinstance(pattern, TpyAsPattern):
|
|
644
|
+
return pattern.pattern, escape_cpp_name(pattern.name), pattern.name
|
|
645
|
+
return pattern, None, None
|
|
646
|
+
|
|
647
|
+
def _gen_switch_default_arm(
|
|
648
|
+
self, out: TextIO, pattern: TpyWildcardPattern | TpyCapturePattern,
|
|
649
|
+
as_name: str | None, as_raw_name: str | None,
|
|
650
|
+
body: list[TpyStmt], indent: str, inner: str,
|
|
651
|
+
type_facts: dict[str, TpyType] | None = None,
|
|
652
|
+
) -> None:
|
|
653
|
+
"""Emit a default: arm in a switch statement."""
|
|
654
|
+
out.write(f"{indent}default: {{\n")
|
|
655
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
656
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
|
|
657
|
+
self._emit_binding(out, as_name, as_raw_name, "__match_subject", inner)
|
|
658
|
+
self.ctx.indent_level += 1
|
|
659
|
+
self._emit_case_body(out, body, type_facts)
|
|
660
|
+
self.ctx.indent_level -= 1
|
|
661
|
+
out.write(f"{inner}break;\n")
|
|
662
|
+
out.write(f"{indent}}}\n")
|
|
663
|
+
|
|
664
|
+
def _gen_match_guarded_union(
|
|
665
|
+
self, out: TextIO, stmt: TpyMatch, subject_type: UnionType, indent: str,
|
|
666
|
+
) -> None:
|
|
667
|
+
"""Generate guarded match on union using switch(index()) with guards inside case blocks.
|
|
668
|
+
|
|
669
|
+
Groups arms by variant type so each type is checked exactly once via
|
|
670
|
+
switch, instead of duplicating holds_alternative per arm.
|
|
671
|
+
"""
|
|
672
|
+
self.ctx.match_counter += 1
|
|
673
|
+
end_label = f"__match_end_{self.ctx.match_counter}"
|
|
674
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
675
|
+
inner2 = INDENT * (self.ctx.indent_level + 2)
|
|
676
|
+
is_ptr_var = self._subject_is_ptr_variant(stmt.subject, subject_type)
|
|
677
|
+
va = VariantAccess("__match_subject", subject_type, is_ptr_variant=is_ptr_var)
|
|
678
|
+
# Variant indices come from the wrapper struct's std::variant ordering;
|
|
679
|
+
# for narrowed recursive unions this is wider than subject_type.members.
|
|
680
|
+
wrapper = subject_type.wrapper_info()
|
|
681
|
+
full_members = wrapper.full_members if wrapper is not None else subject_type.members
|
|
682
|
+
|
|
683
|
+
# Collect arms per variant type index.
|
|
684
|
+
# Each entry: (case, pattern_for_this_type, as_name, as_raw_name)
|
|
685
|
+
type_arms: dict[int, list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]]] = {
|
|
686
|
+
i: [] for i in range(len(full_members))
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
for case in stmt.cases:
|
|
690
|
+
pattern, as_name, as_raw_name = self._unwrap_as_pattern(case.pattern)
|
|
691
|
+
|
|
692
|
+
if isinstance(pattern, TpyClassPattern):
|
|
693
|
+
assert pattern.resolved_type is not None
|
|
694
|
+
idx = self._variant_index(subject_type, pattern.resolved_type)
|
|
695
|
+
type_arms[idx].append((case, pattern, as_name, as_raw_name))
|
|
696
|
+
|
|
697
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
698
|
+
# Track which indices got a class alt from this or-pattern
|
|
699
|
+
# so wildcard alts don't duplicate into the same index
|
|
700
|
+
or_covered: set[int] = set()
|
|
701
|
+
for alt in pattern.patterns:
|
|
702
|
+
if isinstance(alt, TpyClassPattern):
|
|
703
|
+
assert alt.resolved_type is not None
|
|
704
|
+
idx = self._variant_index(subject_type, alt.resolved_type)
|
|
705
|
+
type_arms[idx].append((case, alt, as_name, as_raw_name))
|
|
706
|
+
or_covered.add(idx)
|
|
707
|
+
elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
|
|
708
|
+
for idx in type_arms:
|
|
709
|
+
if idx not in or_covered:
|
|
710
|
+
type_arms[idx].append((case, alt, as_name, as_raw_name))
|
|
711
|
+
else:
|
|
712
|
+
raise CodeGenError(
|
|
713
|
+
f"Unsupported or-pattern alternative: {type(alt).__name__}")
|
|
714
|
+
|
|
715
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
716
|
+
for idx in type_arms:
|
|
717
|
+
type_arms[idx].append((case, pattern, as_name, as_raw_name))
|
|
718
|
+
|
|
719
|
+
else:
|
|
720
|
+
raise CodeGenError(
|
|
721
|
+
f"Unsupported pattern in guarded union match: {type(pattern).__name__}")
|
|
722
|
+
|
|
723
|
+
# Truncate each type's arm list after the first truly unguarded arm
|
|
724
|
+
# (anything after an unguarded arm is unreachable).
|
|
725
|
+
# Arms with union field guards are effectively guarded even if guard is None.
|
|
726
|
+
for idx in type_arms:
|
|
727
|
+
truncated: list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]] = []
|
|
728
|
+
for entry in type_arms[idx]:
|
|
729
|
+
truncated.append(entry)
|
|
730
|
+
pat = entry[1]
|
|
731
|
+
has_field_guard = (isinstance(pat, TpyClassPattern)
|
|
732
|
+
and any(self._sub_has_union_field_guard(sub)
|
|
733
|
+
for _, sub in pat.keywords))
|
|
734
|
+
if entry[0].guard is None and not has_field_guard:
|
|
735
|
+
break
|
|
736
|
+
type_arms[idx] = truncated
|
|
737
|
+
|
|
738
|
+
# Types whose arms are ALL wildcards/captures can share default:.
|
|
739
|
+
# All such indices have identical arm lists because wildcard/capture arms
|
|
740
|
+
# are always broadcast to every index uniformly during collection above.
|
|
741
|
+
default_indices: set[int] = set()
|
|
742
|
+
default_arms: list[tuple[TpyMatchCase, TpyPattern, str | None, str | None]] | None = None
|
|
743
|
+
for idx, arms in type_arms.items():
|
|
744
|
+
if arms and all(isinstance(a[1], (TpyWildcardPattern, TpyCapturePattern))
|
|
745
|
+
for a in arms):
|
|
746
|
+
default_indices.add(idx)
|
|
747
|
+
if default_arms is None:
|
|
748
|
+
default_arms = arms
|
|
749
|
+
|
|
750
|
+
out.write(f"{indent}switch ({va.index_expr()}) {{\n")
|
|
751
|
+
|
|
752
|
+
for idx in range(len(full_members)):
|
|
753
|
+
if idx in default_indices:
|
|
754
|
+
continue
|
|
755
|
+
arms = type_arms[idx]
|
|
756
|
+
if not arms:
|
|
757
|
+
continue
|
|
758
|
+
|
|
759
|
+
out.write(f"{indent}case {idx}: {{\n")
|
|
760
|
+
|
|
761
|
+
# Extract variant value once for this case block
|
|
762
|
+
needs_extraction = any(
|
|
763
|
+
isinstance(a[1], TpyClassPattern)
|
|
764
|
+
and (a[1].keywords or a[0].type_facts or a[2] is not None)
|
|
765
|
+
for a in arms
|
|
766
|
+
)
|
|
767
|
+
case_var = f"__case_{idx}"
|
|
768
|
+
if needs_extraction:
|
|
769
|
+
out.write(f"{inner}auto& {case_var} = {va.get_by_index(idx)};\n")
|
|
770
|
+
|
|
771
|
+
use_scope = len(arms) > 1
|
|
772
|
+
for arm_case, arm_pattern, as_name, as_raw_name in arms:
|
|
773
|
+
self.ctx.emit_source_comment(out, arm_case.loc, inner)
|
|
774
|
+
self._gen_guarded_switch_arm_action(
|
|
775
|
+
out, arm_case, arm_pattern, case_var if needs_extraction else None,
|
|
776
|
+
as_name, as_raw_name, inner, inner2, end_label,
|
|
777
|
+
needs_scope=use_scope,
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
out.write(f"{inner}break;\n")
|
|
781
|
+
out.write(f"{indent}}}\n")
|
|
782
|
+
|
|
783
|
+
if default_indices and default_arms:
|
|
784
|
+
out.write(f"{indent}default: {{\n")
|
|
785
|
+
use_scope = len(default_arms) > 1
|
|
786
|
+
for arm_case, arm_pattern, as_name, as_raw_name in default_arms:
|
|
787
|
+
self.ctx.emit_source_comment(out, arm_case.loc, inner)
|
|
788
|
+
self._gen_guarded_switch_arm_action(
|
|
789
|
+
out, arm_case, arm_pattern, None,
|
|
790
|
+
as_name, as_raw_name, inner, inner2, end_label,
|
|
791
|
+
needs_scope=use_scope,
|
|
792
|
+
)
|
|
793
|
+
out.write(f"{inner}break;\n")
|
|
794
|
+
out.write(f"{indent}}}\n")
|
|
795
|
+
|
|
796
|
+
out.write(f"{indent}}}\n")
|
|
797
|
+
out.write(f"{end_label}:;\n")
|
|
798
|
+
|
|
799
|
+
def _gen_guarded_switch_arm_action(
|
|
800
|
+
self, out: TextIO, arm_case: TpyMatchCase, pattern: TpyPattern,
|
|
801
|
+
case_var: str | None,
|
|
802
|
+
as_name: str | None, as_raw_name: str | None,
|
|
803
|
+
inner: str, inner2: str, end_label: str,
|
|
804
|
+
needs_scope: bool = False,
|
|
805
|
+
) -> None:
|
|
806
|
+
"""Emit a single arm action within a switch case block.
|
|
807
|
+
|
|
808
|
+
When needs_scope is True, wraps bindings+body in { } to avoid name
|
|
809
|
+
conflicts with other arms in the same case block.
|
|
810
|
+
"""
|
|
811
|
+
bind_indent = inner2 if needs_scope else inner
|
|
812
|
+
|
|
813
|
+
if needs_scope:
|
|
814
|
+
out.write(f"{inner}{{\n")
|
|
815
|
+
|
|
816
|
+
# Emit guarded/unguarded body with goto
|
|
817
|
+
has_narrowing = isinstance(pattern, TpyClassPattern)
|
|
818
|
+
saved: dict[str, str | None] = {}
|
|
819
|
+
guard = arm_case.guard
|
|
820
|
+
|
|
821
|
+
# Union field guards generate implicit conditions even without explicit guards
|
|
822
|
+
field_conds = (self._record_field_conditions(pattern, case_var)
|
|
823
|
+
if isinstance(pattern, TpyClassPattern) and case_var else [])
|
|
824
|
+
|
|
825
|
+
# Emit non-union bindings before the condition (guards may reference them).
|
|
826
|
+
# Union field bindings (std::get) must go after the holds_alternative check.
|
|
827
|
+
if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
828
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
829
|
+
self._emit_binding(
|
|
830
|
+
out, escape_cpp_name(pattern.name), pattern.name,
|
|
831
|
+
"__match_subject", bind_indent)
|
|
832
|
+
self._emit_binding(out, as_name, as_raw_name, "__match_subject", bind_indent)
|
|
833
|
+
elif isinstance(pattern, TpyClassPattern) and not field_conds:
|
|
834
|
+
if pattern.keywords:
|
|
835
|
+
self._gen_match_field_bindings(out, pattern, case_var, bind_indent)
|
|
836
|
+
self._emit_binding(out, as_name, as_raw_name, case_var, bind_indent)
|
|
837
|
+
elif not isinstance(pattern, TpyClassPattern):
|
|
838
|
+
raise CodeGenError(
|
|
839
|
+
f"Unsupported pattern in guarded switch arm: {type(pattern).__name__}")
|
|
840
|
+
|
|
841
|
+
if guard is not None or field_conds:
|
|
842
|
+
cond_parts: list[str] = list(field_conds)
|
|
843
|
+
if guard is not None:
|
|
844
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
845
|
+
self.ctx.temps.flush(out, bind_indent)
|
|
846
|
+
cond_parts.append(guard_code)
|
|
847
|
+
out.write(f"{bind_indent}if ({' && '.join(cond_parts)}) {{\n")
|
|
848
|
+
# Emit union field bindings inside the condition block
|
|
849
|
+
body_indent = INDENT * (self.ctx.indent_level + (3 if needs_scope else 2))
|
|
850
|
+
if isinstance(pattern, TpyClassPattern) and field_conds:
|
|
851
|
+
if pattern.keywords:
|
|
852
|
+
self._gen_match_field_bindings(out, pattern, case_var, body_indent)
|
|
853
|
+
self._emit_binding(out, as_name, as_raw_name, case_var, body_indent)
|
|
854
|
+
if has_narrowing:
|
|
855
|
+
saved = self._apply_narrowing(arm_case.type_facts, case_var)
|
|
856
|
+
extra = 3 if needs_scope else 2
|
|
857
|
+
self.ctx.indent_level += extra
|
|
858
|
+
self._emit_case_body(out, arm_case.body, arm_case.type_facts)
|
|
859
|
+
out.write(f"{INDENT * self.ctx.indent_level}goto {end_label};\n")
|
|
860
|
+
self.ctx.indent_level -= extra
|
|
861
|
+
if has_narrowing:
|
|
862
|
+
self.stmts.ctx.restore_narrowed_vars(saved)
|
|
863
|
+
out.write(f"{bind_indent}}}\n")
|
|
864
|
+
else:
|
|
865
|
+
if has_narrowing:
|
|
866
|
+
saved = self._apply_narrowing(arm_case.type_facts, case_var)
|
|
867
|
+
extra = 2 if needs_scope else 1
|
|
868
|
+
self.ctx.indent_level += extra
|
|
869
|
+
self._emit_case_body(out, arm_case.body, arm_case.type_facts)
|
|
870
|
+
out.write(f"{INDENT * self.ctx.indent_level}goto {end_label};\n")
|
|
871
|
+
self.ctx.indent_level -= extra
|
|
872
|
+
if has_narrowing:
|
|
873
|
+
self.stmts.ctx.restore_narrowed_vars(saved)
|
|
874
|
+
|
|
875
|
+
if needs_scope:
|
|
876
|
+
out.write(f"{inner}}}\n")
|
|
877
|
+
|
|
878
|
+
def _apply_narrowing(
|
|
879
|
+
self, type_facts: dict[str, TpyType] | None, case_var: str | None,
|
|
880
|
+
) -> dict[str, str | None]:
|
|
881
|
+
"""Apply narrowing facts and return saved state for later restoration."""
|
|
882
|
+
saved: dict[str, str | None] = {}
|
|
883
|
+
if type_facts:
|
|
884
|
+
for var_name in type_facts:
|
|
885
|
+
saved[var_name] = self.ctx.narrowed_vars.get(var_name)
|
|
886
|
+
self.ctx.narrowed_vars[var_name] = case_var
|
|
887
|
+
return saved
|
|
888
|
+
|
|
889
|
+
def _emit_case_body(
|
|
890
|
+
self, out: TextIO, body: list['TpyStmt'],
|
|
891
|
+
type_facts: dict[str, TpyType] | None = None,
|
|
892
|
+
) -> None:
|
|
893
|
+
"""Emit a case body with literal_facts/protocol_narrowings bounded to the case scope.
|
|
894
|
+
|
|
895
|
+
Persistent narrowings introduced inside a body (e.g. `assert isinstance(x, P)`)
|
|
896
|
+
would otherwise bleed into later cases or post-match code. Snapshotting and
|
|
897
|
+
restoring around body emission keeps them scoped to this case.
|
|
898
|
+
|
|
899
|
+
When type_facts is provided, LiteralType facts are pushed for dead-branch
|
|
900
|
+
elimination. narrowed_vars std::get extractions are managed separately by
|
|
901
|
+
the caller via _apply_narrowing (they depend on a case_var).
|
|
902
|
+
"""
|
|
903
|
+
proto_saved = self.ctx.save_protocol_narrowings()
|
|
904
|
+
lit_saved = self.ctx.save_literal_facts()
|
|
905
|
+
# Persistent isinstance aliases (assert / early-return) emitted inside
|
|
906
|
+
# the case body live only in this case's C++ scope; restore both the
|
|
907
|
+
# alias-name set and narrowed_vars so subsequent cases / post-match
|
|
908
|
+
# code don't reference out-of-scope aliases.
|
|
909
|
+
narrowed_saved = dict(self.ctx.narrowed_vars)
|
|
910
|
+
alias_saved = self.ctx.declared_persistent_aliases.copy()
|
|
911
|
+
if type_facts:
|
|
912
|
+
for var_name, ty in type_facts.items():
|
|
913
|
+
if isinstance(ty, LiteralType):
|
|
914
|
+
self.ctx.literal_facts[var_name] = ty
|
|
915
|
+
try:
|
|
916
|
+
# Resumable-frame `match` (H1): when a suspending match is
|
|
917
|
+
# being emitted, this arm's body is not emitted inline -- it
|
|
918
|
+
# lives in the state machine and is routed back through the
|
|
919
|
+
# resumable walker. Identify the arm by its body's identity.
|
|
920
|
+
arm_emitter = self.ctx.resumable_arm_emitter
|
|
921
|
+
arm_bb = (self.ctx.resumable_arm_bb_by_body.get(id(body))
|
|
922
|
+
if arm_emitter is not None else None)
|
|
923
|
+
if arm_emitter is not None and arm_bb is not None:
|
|
924
|
+
arm_emitter(arm_bb)
|
|
925
|
+
else:
|
|
926
|
+
for s in body:
|
|
927
|
+
self.stmts.gen_stmt(out, s)
|
|
928
|
+
finally:
|
|
929
|
+
self.ctx.narrowed_vars = narrowed_saved
|
|
930
|
+
self.ctx.declared_persistent_aliases = alias_saved
|
|
931
|
+
self.ctx.restore_literal_facts(lit_saved)
|
|
932
|
+
self.ctx.restore_protocol_narrowings(proto_saved)
|
|
933
|
+
|
|
934
|
+
def _gen_match_if_elif(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
935
|
+
"""Generate match/case as an if/elif/else chain (for str and float subjects)."""
|
|
936
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
937
|
+
|
|
938
|
+
for i, case in enumerate(stmt.cases):
|
|
939
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
940
|
+
keyword = "if" if i == 0 else "} else if"
|
|
941
|
+
pattern = case.pattern
|
|
942
|
+
guard = case.guard
|
|
943
|
+
|
|
944
|
+
if isinstance(pattern, TpyLiteralPattern):
|
|
945
|
+
cond = self._gen_literal_cond(pattern)
|
|
946
|
+
if guard is not None:
|
|
947
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
948
|
+
self.ctx.temps.flush(out, indent)
|
|
949
|
+
cond = f"{cond} && {guard_code}"
|
|
950
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
951
|
+
self.ctx.indent_level += 1
|
|
952
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
953
|
+
self.ctx.indent_level -= 1
|
|
954
|
+
|
|
955
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
956
|
+
conds = []
|
|
957
|
+
for alt in pattern.patterns:
|
|
958
|
+
if isinstance(alt, TpyLiteralPattern):
|
|
959
|
+
conds.append(self._gen_literal_cond(alt))
|
|
960
|
+
else:
|
|
961
|
+
raise CodeGenError(f"Unsupported or-pattern alternative: {type(alt).__name__}")
|
|
962
|
+
cond = " || ".join(conds)
|
|
963
|
+
if guard is not None:
|
|
964
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
965
|
+
self.ctx.temps.flush(out, indent)
|
|
966
|
+
cond = f"({cond}) && {guard_code}"
|
|
967
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
968
|
+
self.ctx.indent_level += 1
|
|
969
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
970
|
+
self.ctx.indent_level -= 1
|
|
971
|
+
|
|
972
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
973
|
+
# Emit capture binding before the guard check so the guard
|
|
974
|
+
# can reference the captured variable.
|
|
975
|
+
if isinstance(pattern, TpyCapturePattern) and guard is not None:
|
|
976
|
+
if i > 0:
|
|
977
|
+
out.write(f"{indent}}}\n")
|
|
978
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", indent)
|
|
979
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
980
|
+
self.ctx.temps.flush(out, indent)
|
|
981
|
+
out.write(f"{indent}if ({guard_code}) {{\n")
|
|
982
|
+
elif guard is not None:
|
|
983
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
984
|
+
self.ctx.temps.flush(out, indent)
|
|
985
|
+
out.write(f"{indent}{keyword} ({guard_code}) {{\n")
|
|
986
|
+
elif i == 0:
|
|
987
|
+
out.write(f"{indent}{{\n")
|
|
988
|
+
else:
|
|
989
|
+
out.write(f"{indent}}} else {{\n")
|
|
990
|
+
if isinstance(pattern, TpyCapturePattern) and guard is None:
|
|
991
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
|
|
992
|
+
self.ctx.indent_level += 1
|
|
993
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
994
|
+
self.ctx.indent_level -= 1
|
|
995
|
+
|
|
996
|
+
elif isinstance(pattern, TpyAsPattern):
|
|
997
|
+
inner_pat = pattern.pattern
|
|
998
|
+
as_name = escape_cpp_name(pattern.name)
|
|
999
|
+
if isinstance(inner_pat, (TpyLiteralPattern, TpyValuePattern)):
|
|
1000
|
+
if isinstance(inner_pat, TpyLiteralPattern):
|
|
1001
|
+
cond = self._gen_literal_cond(inner_pat)
|
|
1002
|
+
else:
|
|
1003
|
+
val_code = self.expressions.gen_expr(inner_pat.expr)
|
|
1004
|
+
self.ctx.temps.flush(out, indent)
|
|
1005
|
+
cond = f"__match_subject == {val_code}"
|
|
1006
|
+
if guard is not None:
|
|
1007
|
+
# Guard may reference as-variable; split into match + bind + guard
|
|
1008
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1009
|
+
self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
|
|
1010
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1011
|
+
self.ctx.temps.flush(out, inner)
|
|
1012
|
+
out.write(f"{inner}if ({guard_code}) {{\n")
|
|
1013
|
+
self.ctx.indent_level += 2
|
|
1014
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1015
|
+
self.ctx.indent_level -= 2
|
|
1016
|
+
out.write(f"{inner}}}\n")
|
|
1017
|
+
else:
|
|
1018
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1019
|
+
self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
|
|
1020
|
+
self.ctx.indent_level += 1
|
|
1021
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1022
|
+
self.ctx.indent_level -= 1
|
|
1023
|
+
elif isinstance(inner_pat, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1024
|
+
if guard is not None:
|
|
1025
|
+
# Emit binding before guard (like capture+guard path)
|
|
1026
|
+
if i > 0:
|
|
1027
|
+
out.write(f"{indent}}}\n")
|
|
1028
|
+
self._emit_binding(out, as_name, pattern.name, "__match_subject", indent)
|
|
1029
|
+
if isinstance(inner_pat, TpyCapturePattern):
|
|
1030
|
+
self._emit_binding(out, escape_cpp_name(inner_pat.name), inner_pat.name, "__match_subject", indent)
|
|
1031
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1032
|
+
self.ctx.temps.flush(out, indent)
|
|
1033
|
+
out.write(f"{indent}if ({guard_code}) {{\n")
|
|
1034
|
+
else:
|
|
1035
|
+
if i == 0:
|
|
1036
|
+
out.write(f"{indent}{{\n")
|
|
1037
|
+
else:
|
|
1038
|
+
out.write(f"{indent}}} else {{\n")
|
|
1039
|
+
self._emit_binding(out, as_name, pattern.name, "__match_subject", inner)
|
|
1040
|
+
if isinstance(inner_pat, TpyCapturePattern):
|
|
1041
|
+
self._emit_binding(out, escape_cpp_name(inner_pat.name), inner_pat.name, "__match_subject", inner)
|
|
1042
|
+
self.ctx.indent_level += 1
|
|
1043
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1044
|
+
self.ctx.indent_level -= 1
|
|
1045
|
+
else:
|
|
1046
|
+
raise CodeGenError(f"Unsupported as-pattern inner: {type(inner_pat).__name__}")
|
|
1047
|
+
|
|
1048
|
+
else:
|
|
1049
|
+
raise CodeGenError(f"Unsupported match pattern: {type(pattern).__name__}")
|
|
1050
|
+
|
|
1051
|
+
out.write(f"{indent}}}\n")
|
|
1052
|
+
|
|
1053
|
+
def _should_switch_str(self, stmt: TpyMatch) -> bool:
|
|
1054
|
+
"""Check if a string match has enough unguarded literal cases for switch dispatch."""
|
|
1055
|
+
count = 0
|
|
1056
|
+
for case in stmt.cases:
|
|
1057
|
+
if case.guard is not None:
|
|
1058
|
+
continue
|
|
1059
|
+
pat = case.pattern
|
|
1060
|
+
if isinstance(pat, TpyAsPattern):
|
|
1061
|
+
pat = pat.pattern
|
|
1062
|
+
if isinstance(pat, TpyLiteralPattern) and isinstance(pat.value, str):
|
|
1063
|
+
count += 1
|
|
1064
|
+
elif isinstance(pat, TpyOrPattern):
|
|
1065
|
+
if all(isinstance(a, TpyLiteralPattern) and isinstance(a.value, str)
|
|
1066
|
+
for a in pat.patterns):
|
|
1067
|
+
count += len(pat.patterns)
|
|
1068
|
+
return count >= STRING_SWITCH_THRESHOLD
|
|
1069
|
+
|
|
1070
|
+
def _gen_match_switch_str(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
1071
|
+
"""Generate optimized switch-based dispatch for string match/case."""
|
|
1072
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
1073
|
+
deep = INDENT * (self.ctx.indent_level + 2)
|
|
1074
|
+
|
|
1075
|
+
self.ctx.match_counter += 1
|
|
1076
|
+
end_label = f"__match_end_{self.ctx.match_counter}"
|
|
1077
|
+
|
|
1078
|
+
# Partition cases into guarded literals, unguarded literals, and trailing
|
|
1079
|
+
guarded: list[TpyMatchCase] = []
|
|
1080
|
+
# Each unguarded entry: (case, list_of_string_values)
|
|
1081
|
+
unguarded: list[tuple[TpyMatchCase, list[str]]] = []
|
|
1082
|
+
trailing: list[TpyMatchCase] = []
|
|
1083
|
+
|
|
1084
|
+
for case in stmt.cases:
|
|
1085
|
+
pat = case.pattern
|
|
1086
|
+
if isinstance(pat, TpyAsPattern):
|
|
1087
|
+
pat = pat.pattern
|
|
1088
|
+
is_str_lit = isinstance(pat, TpyLiteralPattern) and isinstance(pat.value, str)
|
|
1089
|
+
is_str_or = (isinstance(pat, TpyOrPattern) and
|
|
1090
|
+
all(isinstance(a, TpyLiteralPattern) and isinstance(a.value, str)
|
|
1091
|
+
for a in pat.patterns))
|
|
1092
|
+
if is_str_lit or is_str_or:
|
|
1093
|
+
if case.guard is not None:
|
|
1094
|
+
guarded.append(case)
|
|
1095
|
+
else:
|
|
1096
|
+
strs = [pat.value] if is_str_lit else [a.value for a in pat.patterns]
|
|
1097
|
+
unguarded.append((case, strs))
|
|
1098
|
+
else:
|
|
1099
|
+
trailing.append(case)
|
|
1100
|
+
|
|
1101
|
+
# Collect all strings and find best discriminator
|
|
1102
|
+
all_strings = []
|
|
1103
|
+
for _, strs in unguarded:
|
|
1104
|
+
all_strings.extend(strs)
|
|
1105
|
+
kind, param, _buckets = find_best_discriminator(all_strings)
|
|
1106
|
+
|
|
1107
|
+
# Build bucket -> [(case, string_value)] mapping, preserving arm order
|
|
1108
|
+
bucket_entries: dict[int, list[tuple[TpyMatchCase, str]]] = defaultdict(list)
|
|
1109
|
+
for case, strs in unguarded:
|
|
1110
|
+
for s in strs:
|
|
1111
|
+
key = len(s) if kind == "length" else ord(s[param])
|
|
1112
|
+
bucket_entries[key].append((case, s))
|
|
1113
|
+
|
|
1114
|
+
# Emit guarded string literal arms first (pre-switch, in original order)
|
|
1115
|
+
for case in guarded:
|
|
1116
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1117
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1118
|
+
if isinstance(pattern, TpyLiteralPattern):
|
|
1119
|
+
cond = self._gen_literal_cond(pattern)
|
|
1120
|
+
else:
|
|
1121
|
+
# Or-pattern
|
|
1122
|
+
conds = [self._gen_literal_cond(a) for a in pattern.patterns]
|
|
1123
|
+
cond = " || ".join(conds)
|
|
1124
|
+
guard_code = self.expressions.gen_expr(case.guard)
|
|
1125
|
+
self.ctx.temps.flush(out, indent)
|
|
1126
|
+
is_or = isinstance(pattern, TpyOrPattern)
|
|
1127
|
+
cond = f"({cond}) && {guard_code}" if is_or else f"{cond} && {guard_code}"
|
|
1128
|
+
out.write(f"{indent}if ({cond}) {{\n")
|
|
1129
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1130
|
+
self.ctx.indent_level += 1
|
|
1131
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1132
|
+
self.ctx.indent_level -= 1
|
|
1133
|
+
out.write(f"{inner}goto {end_label};\n")
|
|
1134
|
+
out.write(f"{indent}}}\n")
|
|
1135
|
+
|
|
1136
|
+
# Emit switch on discriminator
|
|
1137
|
+
# For char_at, wrap in an if-guard so short strings skip the switch
|
|
1138
|
+
sw_indent = indent
|
|
1139
|
+
sw_inner = inner
|
|
1140
|
+
sw_deep = deep
|
|
1141
|
+
if kind == "char_at":
|
|
1142
|
+
out.write(f"{indent}if (__match_subject.size() >= {param + 1}) {{\n")
|
|
1143
|
+
sw_indent = inner
|
|
1144
|
+
sw_inner = deep
|
|
1145
|
+
sw_deep = INDENT * (self.ctx.indent_level + 3)
|
|
1146
|
+
out.write(f"{sw_indent}switch (static_cast<unsigned char>(__match_subject[{param}])) {{\n")
|
|
1147
|
+
else:
|
|
1148
|
+
out.write(f"{indent}switch (__match_subject.size()) {{\n")
|
|
1149
|
+
|
|
1150
|
+
for disc_value in sorted(bucket_entries.keys()):
|
|
1151
|
+
entries = bucket_entries[disc_value]
|
|
1152
|
+
if kind == "char_at":
|
|
1153
|
+
ch = chr(disc_value)
|
|
1154
|
+
out.write(f"{sw_indent}case '{escape_cpp_char(ch)}': {{\n")
|
|
1155
|
+
else:
|
|
1156
|
+
out.write(f"{sw_indent}case {disc_value}: {{\n")
|
|
1157
|
+
for case, string_val in entries:
|
|
1158
|
+
self.ctx.emit_source_comment(out, case.loc, sw_inner)
|
|
1159
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1160
|
+
out.write(f'{sw_inner}if (__match_subject == {cpp_string_literal_expr(string_val)}) {{\n')
|
|
1161
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", sw_deep)
|
|
1162
|
+
self.ctx.indent_level += (3 if kind == "char_at" else 2)
|
|
1163
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1164
|
+
self.ctx.indent_level -= (3 if kind == "char_at" else 2)
|
|
1165
|
+
out.write(f"{sw_deep}goto {end_label};\n")
|
|
1166
|
+
out.write(f"{sw_inner}}}\n")
|
|
1167
|
+
out.write(f"{sw_inner}break;\n")
|
|
1168
|
+
out.write(f"{sw_indent}}}\n")
|
|
1169
|
+
|
|
1170
|
+
out.write(f"{sw_indent}}}\n") # close switch
|
|
1171
|
+
if kind == "char_at":
|
|
1172
|
+
out.write(f"{indent}}}\n") # close if-guard
|
|
1173
|
+
|
|
1174
|
+
# Emit trailing arms (wildcard, capture, etc.) in a block scope
|
|
1175
|
+
# to prevent goto from crossing variable declarations
|
|
1176
|
+
if trailing:
|
|
1177
|
+
out.write(f"{indent}{{\n")
|
|
1178
|
+
for case in trailing:
|
|
1179
|
+
self.ctx.emit_source_comment(out, case.loc, inner)
|
|
1180
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1181
|
+
if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1182
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1183
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
|
|
1184
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1185
|
+
if case.guard is not None:
|
|
1186
|
+
guard_code = self.expressions.gen_expr(case.guard)
|
|
1187
|
+
self.ctx.temps.flush(out, inner)
|
|
1188
|
+
out.write(f"{inner}if ({guard_code}) {{\n")
|
|
1189
|
+
self.ctx.indent_level += 2
|
|
1190
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1191
|
+
self.ctx.indent_level -= 2
|
|
1192
|
+
out.write(f"{inner}}}\n")
|
|
1193
|
+
else:
|
|
1194
|
+
self.ctx.indent_level += 1
|
|
1195
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1196
|
+
self.ctx.indent_level -= 1
|
|
1197
|
+
else:
|
|
1198
|
+
raise CodeGenError(
|
|
1199
|
+
f"Unsupported trailing pattern in string switch: {type(pattern).__name__}"
|
|
1200
|
+
)
|
|
1201
|
+
out.write(f"{indent}}}\n")
|
|
1202
|
+
|
|
1203
|
+
out.write(f"{indent}{end_label}:;\n")
|
|
1204
|
+
|
|
1205
|
+
def _gen_match_if_elif_record(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
1206
|
+
"""Generate match/case as if/elif chain for concrete record subjects (no guards)."""
|
|
1207
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
1208
|
+
|
|
1209
|
+
for i, case in enumerate(stmt.cases):
|
|
1210
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1211
|
+
keyword = "if" if i == 0 else "} else if"
|
|
1212
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1213
|
+
|
|
1214
|
+
if isinstance(pattern, TpyClassPattern):
|
|
1215
|
+
conds = self._record_field_conditions(pattern)
|
|
1216
|
+
if conds:
|
|
1217
|
+
out.write(f"{indent}{keyword} ({' && '.join(conds)}) {{\n")
|
|
1218
|
+
elif i == 0:
|
|
1219
|
+
out.write(f"{indent}{{\n")
|
|
1220
|
+
else:
|
|
1221
|
+
out.write(f"{indent}}} else {{\n")
|
|
1222
|
+
self._gen_match_field_bindings(out, pattern, "__match_subject", inner)
|
|
1223
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1224
|
+
self.ctx.indent_level += 1
|
|
1225
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1226
|
+
self.ctx.indent_level -= 1
|
|
1227
|
+
|
|
1228
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
1229
|
+
or_parts: list[str] = []
|
|
1230
|
+
for alt in pattern.patterns:
|
|
1231
|
+
if isinstance(alt, TpyClassPattern):
|
|
1232
|
+
alt_conds = self._record_field_conditions(alt)
|
|
1233
|
+
if alt_conds:
|
|
1234
|
+
or_parts.append("(" + " && ".join(alt_conds) + ")")
|
|
1235
|
+
elif isinstance(alt, TpyWildcardPattern):
|
|
1236
|
+
or_parts.clear()
|
|
1237
|
+
break
|
|
1238
|
+
else:
|
|
1239
|
+
raise CodeGenError(
|
|
1240
|
+
f"Unsupported or-pattern alternative for record: "
|
|
1241
|
+
f"{type(alt).__name__}"
|
|
1242
|
+
)
|
|
1243
|
+
if or_parts:
|
|
1244
|
+
out.write(f"{indent}{keyword} ({' || '.join(or_parts)}) {{\n")
|
|
1245
|
+
elif i == 0:
|
|
1246
|
+
out.write(f"{indent}{{\n")
|
|
1247
|
+
else:
|
|
1248
|
+
out.write(f"{indent}}} else {{\n")
|
|
1249
|
+
self.ctx.indent_level += 1
|
|
1250
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1251
|
+
self.ctx.indent_level -= 1
|
|
1252
|
+
|
|
1253
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1254
|
+
if i == 0:
|
|
1255
|
+
out.write(f"{indent}{{\n")
|
|
1256
|
+
else:
|
|
1257
|
+
out.write(f"{indent}}} else {{\n")
|
|
1258
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1259
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", inner)
|
|
1260
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1261
|
+
self.ctx.indent_level += 1
|
|
1262
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1263
|
+
self.ctx.indent_level -= 1
|
|
1264
|
+
|
|
1265
|
+
else:
|
|
1266
|
+
raise CodeGenError(f"Unsupported match pattern for record: {type(pattern).__name__}")
|
|
1267
|
+
|
|
1268
|
+
out.write(f"{indent}}}\n")
|
|
1269
|
+
|
|
1270
|
+
def _gen_match_guarded_record(self, out: TextIO, stmt: TpyMatch, indent: str) -> None:
|
|
1271
|
+
"""Generate match/case for record subjects with guards using standalone ifs + goto."""
|
|
1272
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
1273
|
+
self.ctx.match_counter += 1
|
|
1274
|
+
end_label = f"__match_end_{self.ctx.match_counter}"
|
|
1275
|
+
|
|
1276
|
+
for i, case in enumerate(stmt.cases):
|
|
1277
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1278
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1279
|
+
guard = case.guard
|
|
1280
|
+
|
|
1281
|
+
if isinstance(pattern, TpyClassPattern):
|
|
1282
|
+
conds = self._record_field_conditions(pattern)
|
|
1283
|
+
if conds:
|
|
1284
|
+
out.write(f"{indent}if ({' && '.join(conds)}) {{\n")
|
|
1285
|
+
else:
|
|
1286
|
+
out.write(f"{indent}{{\n")
|
|
1287
|
+
self._gen_match_field_bindings(out, pattern, "__match_subject", inner)
|
|
1288
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1289
|
+
if guard is not None:
|
|
1290
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1291
|
+
self.ctx.temps.flush(out, inner)
|
|
1292
|
+
out.write(f"{inner}if ({guard_code}) {{\n")
|
|
1293
|
+
self.ctx.indent_level += 2
|
|
1294
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1295
|
+
self.ctx.indent_level -= 2
|
|
1296
|
+
out.write(f"{inner} goto {end_label};\n")
|
|
1297
|
+
out.write(f"{inner}}}\n")
|
|
1298
|
+
else:
|
|
1299
|
+
self.ctx.indent_level += 1
|
|
1300
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1301
|
+
self.ctx.indent_level -= 1
|
|
1302
|
+
out.write(f"{inner}goto {end_label};\n")
|
|
1303
|
+
out.write(f"{indent}}}\n")
|
|
1304
|
+
|
|
1305
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1306
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1307
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, "__match_subject", indent)
|
|
1308
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", indent)
|
|
1309
|
+
if guard is not None:
|
|
1310
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1311
|
+
self.ctx.temps.flush(out, indent)
|
|
1312
|
+
out.write(f"{indent}if ({guard_code}) {{\n")
|
|
1313
|
+
self.ctx.indent_level += 1
|
|
1314
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1315
|
+
self.ctx.indent_level -= 1
|
|
1316
|
+
out.write(f"{inner}goto {end_label};\n")
|
|
1317
|
+
out.write(f"{indent}}}\n")
|
|
1318
|
+
else:
|
|
1319
|
+
out.write(f"{indent}{{\n")
|
|
1320
|
+
self.ctx.indent_level += 1
|
|
1321
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1322
|
+
self.ctx.indent_level -= 1
|
|
1323
|
+
out.write(f"{indent}}}\n")
|
|
1324
|
+
|
|
1325
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
1326
|
+
or_parts: list[str] = []
|
|
1327
|
+
for alt in pattern.patterns:
|
|
1328
|
+
if isinstance(alt, TpyClassPattern):
|
|
1329
|
+
alt_conds = self._record_field_conditions(alt)
|
|
1330
|
+
if alt_conds:
|
|
1331
|
+
or_parts.append("(" + " && ".join(alt_conds) + ")")
|
|
1332
|
+
elif isinstance(alt, TpyWildcardPattern):
|
|
1333
|
+
or_parts.clear()
|
|
1334
|
+
break
|
|
1335
|
+
else:
|
|
1336
|
+
raise CodeGenError(
|
|
1337
|
+
f"Unsupported or-pattern alternative for record: "
|
|
1338
|
+
f"{type(alt).__name__}"
|
|
1339
|
+
)
|
|
1340
|
+
if or_parts:
|
|
1341
|
+
cond = " || ".join(or_parts)
|
|
1342
|
+
if guard is not None:
|
|
1343
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1344
|
+
self.ctx.temps.flush(out, indent)
|
|
1345
|
+
cond = f"({cond}) && {guard_code}"
|
|
1346
|
+
out.write(f"{indent}if ({cond}) {{\n")
|
|
1347
|
+
else:
|
|
1348
|
+
if guard is not None:
|
|
1349
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1350
|
+
self.ctx.temps.flush(out, indent)
|
|
1351
|
+
out.write(f"{indent}if ({guard_code}) {{\n")
|
|
1352
|
+
else:
|
|
1353
|
+
out.write(f"{indent}{{\n")
|
|
1354
|
+
self.ctx.indent_level += 1
|
|
1355
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1356
|
+
self.ctx.indent_level -= 1
|
|
1357
|
+
out.write(f"{inner}goto {end_label};\n")
|
|
1358
|
+
out.write(f"{indent}}}\n")
|
|
1359
|
+
|
|
1360
|
+
else:
|
|
1361
|
+
raise CodeGenError(f"Unsupported match pattern for record: {type(pattern).__name__}")
|
|
1362
|
+
|
|
1363
|
+
out.write(f"{indent}{end_label}:;\n")
|
|
1364
|
+
|
|
1365
|
+
|
|
1366
|
+
# ------------------------------------------------------------------
|
|
1367
|
+
# Optimized Optional match: hoist null check, dispatch inner
|
|
1368
|
+
# ------------------------------------------------------------------
|
|
1369
|
+
|
|
1370
|
+
def _partition_optional_cases(
|
|
1371
|
+
self, cases: list['TpyMatchCase'],
|
|
1372
|
+
) -> tuple[list['TpyMatchCase'], list['TpyMatchCase']] | None:
|
|
1373
|
+
"""Split cases into (none_cases, inner_cases) if None arms form a prefix.
|
|
1374
|
+
|
|
1375
|
+
Returns None if the optimization cannot be applied:
|
|
1376
|
+
- None arms don't form a contiguous prefix
|
|
1377
|
+
- An or-pattern mixes None and non-None alternatives
|
|
1378
|
+
- A None arm has a guard (guard failure needs fallthrough to later arms)
|
|
1379
|
+
"""
|
|
1380
|
+
none_cases: list[TpyMatchCase] = []
|
|
1381
|
+
inner_cases: list[TpyMatchCase] = []
|
|
1382
|
+
seen_inner = False
|
|
1383
|
+
|
|
1384
|
+
for case in cases:
|
|
1385
|
+
pat = case.pattern
|
|
1386
|
+
if isinstance(pat, TpyAsPattern):
|
|
1387
|
+
pat = pat.pattern
|
|
1388
|
+
|
|
1389
|
+
# Or-pattern mixing None and non-None -- bail out
|
|
1390
|
+
if isinstance(pat, TpyOrPattern):
|
|
1391
|
+
has_none = any(
|
|
1392
|
+
isinstance(a, TpyLiteralPattern) and a.value is None
|
|
1393
|
+
for a in pat.patterns
|
|
1394
|
+
)
|
|
1395
|
+
has_other = any(
|
|
1396
|
+
not (isinstance(a, TpyLiteralPattern) and a.value is None)
|
|
1397
|
+
for a in pat.patterns
|
|
1398
|
+
)
|
|
1399
|
+
if has_none and has_other:
|
|
1400
|
+
return None
|
|
1401
|
+
if has_none:
|
|
1402
|
+
if seen_inner:
|
|
1403
|
+
return None
|
|
1404
|
+
if case.guard is not None:
|
|
1405
|
+
return None
|
|
1406
|
+
none_cases.append(case)
|
|
1407
|
+
else:
|
|
1408
|
+
seen_inner = True
|
|
1409
|
+
inner_cases.append(case)
|
|
1410
|
+
continue
|
|
1411
|
+
|
|
1412
|
+
is_none = isinstance(pat, TpyLiteralPattern) and pat.value is None
|
|
1413
|
+
if is_none:
|
|
1414
|
+
if seen_inner:
|
|
1415
|
+
return None
|
|
1416
|
+
# Guarded None arm needs fallthrough to later arms on guard failure
|
|
1417
|
+
if case.guard is not None:
|
|
1418
|
+
return None
|
|
1419
|
+
none_cases.append(case)
|
|
1420
|
+
else:
|
|
1421
|
+
seen_inner = True
|
|
1422
|
+
inner_cases.append(case)
|
|
1423
|
+
|
|
1424
|
+
if not inner_cases:
|
|
1425
|
+
return None
|
|
1426
|
+
return none_cases, inner_cases
|
|
1427
|
+
|
|
1428
|
+
def _gen_match_optimized_optional(
|
|
1429
|
+
self, out: TextIO, stmt: TpyMatch, subject_type: OptionalType,
|
|
1430
|
+
none_cases: list['TpyMatchCase'], inner_cases: list['TpyMatchCase'],
|
|
1431
|
+
indent: str,
|
|
1432
|
+
) -> None:
|
|
1433
|
+
"""Generate optimized Optional match: if (null) { ... } else { dispatch }."""
|
|
1434
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
1435
|
+
uses_ptr = subject_type.uses_pointer_repr()
|
|
1436
|
+
null_cond = "__match_subject == nullptr" if uses_ptr else "!__match_subject.has_value()"
|
|
1437
|
+
|
|
1438
|
+
# --- None branch ---
|
|
1439
|
+
has_value_cond = "__match_subject != nullptr" if uses_ptr else "__match_subject.has_value()"
|
|
1440
|
+
if not none_cases:
|
|
1441
|
+
# No None arms -- just guard on has_value, no else
|
|
1442
|
+
out.write(f"{indent}if ({has_value_cond}) {{\n")
|
|
1443
|
+
elif len(none_cases) == 1 and none_cases[0].guard is None:
|
|
1444
|
+
# Simple: single unguarded None arm
|
|
1445
|
+
case = none_cases[0]
|
|
1446
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1447
|
+
out.write(f"{indent}if ({null_cond}) {{\n")
|
|
1448
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1449
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1450
|
+
self.ctx.indent_level += 1
|
|
1451
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1452
|
+
self.ctx.indent_level -= 1
|
|
1453
|
+
else:
|
|
1454
|
+
# Multiple or guarded None arms: guard chain inside null block
|
|
1455
|
+
self.ctx.emit_source_comment(out, none_cases[0].loc, indent)
|
|
1456
|
+
out.write(f"{indent}if ({null_cond}) {{\n")
|
|
1457
|
+
for j, case in enumerate(none_cases):
|
|
1458
|
+
if j > 0:
|
|
1459
|
+
self.ctx.emit_source_comment(out, case.loc, inner)
|
|
1460
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1461
|
+
if case.guard is not None:
|
|
1462
|
+
guard_code = self.expressions.gen_expr(case.guard)
|
|
1463
|
+
self.ctx.temps.flush(out, inner)
|
|
1464
|
+
kw = "if" if j == 0 else "} else if"
|
|
1465
|
+
out.write(f"{inner}{kw} ({guard_code}) {{\n")
|
|
1466
|
+
else:
|
|
1467
|
+
if j == 0:
|
|
1468
|
+
pass # body goes directly in null block
|
|
1469
|
+
else:
|
|
1470
|
+
out.write(f"{inner}}} else {{\n")
|
|
1471
|
+
deep = INDENT * (self.ctx.indent_level + 2) if case.guard is not None or j > 0 else inner
|
|
1472
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", deep)
|
|
1473
|
+
extra = 2 if case.guard is not None or j > 0 else 1
|
|
1474
|
+
self.ctx.indent_level += extra
|
|
1475
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1476
|
+
self.ctx.indent_level -= extra
|
|
1477
|
+
if any(c.guard is not None for c in none_cases):
|
|
1478
|
+
out.write(f"{inner}}}\n")
|
|
1479
|
+
|
|
1480
|
+
# --- Inner value dispatch ---
|
|
1481
|
+
if none_cases:
|
|
1482
|
+
out.write(f"{indent}}} else {{\n")
|
|
1483
|
+
deref = "(*__match_subject)"
|
|
1484
|
+
out.write(f"{inner}auto& __match_inner = {deref};\n")
|
|
1485
|
+
|
|
1486
|
+
inner_type = subject_type.inner
|
|
1487
|
+
if is_enum_type(inner_type):
|
|
1488
|
+
groups = self._group_switch_arms(inner_cases, kind="enum")
|
|
1489
|
+
self.ctx.indent_level += 1
|
|
1490
|
+
self._emit_switch_groups(out, groups, inner, subject_expr="__match_inner")
|
|
1491
|
+
self.ctx.indent_level -= 1
|
|
1492
|
+
elif is_fixed_int_type(inner_type) or is_bool_type(inner_type):
|
|
1493
|
+
groups = self._group_switch_arms(inner_cases, kind="primitive")
|
|
1494
|
+
self.ctx.indent_level += 1
|
|
1495
|
+
self._emit_switch_groups(out, groups, inner, subject_expr="__match_inner")
|
|
1496
|
+
self.ctx.indent_level -= 1
|
|
1497
|
+
elif isinstance(inner_type, NominalType) and inner_type.is_user_record:
|
|
1498
|
+
self._emit_optional_inner_record(out, inner_cases, inner)
|
|
1499
|
+
else:
|
|
1500
|
+
# str, float, other: if/elif chain on __match_inner
|
|
1501
|
+
self._emit_optional_inner_if_elif(out, inner_cases, inner)
|
|
1502
|
+
|
|
1503
|
+
out.write(f"{indent}}}\n")
|
|
1504
|
+
|
|
1505
|
+
def _emit_optional_inner_record(
|
|
1506
|
+
self, out: TextIO, cases: list['TpyMatchCase'], indent: str,
|
|
1507
|
+
) -> None:
|
|
1508
|
+
"""Emit if/elif chain for record patterns on dereferenced Optional."""
|
|
1509
|
+
inner = INDENT * (self.ctx.indent_level + 2)
|
|
1510
|
+
subject_expr = "__match_inner"
|
|
1511
|
+
|
|
1512
|
+
for i, case in enumerate(cases):
|
|
1513
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1514
|
+
keyword = "if" if i == 0 else "} else if"
|
|
1515
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1516
|
+
guard = case.guard
|
|
1517
|
+
|
|
1518
|
+
if isinstance(pattern, TpyClassPattern):
|
|
1519
|
+
field_conds = self._record_field_conditions(pattern, subject_expr)
|
|
1520
|
+
cond = " && ".join(field_conds) if field_conds else "true"
|
|
1521
|
+
if guard is not None:
|
|
1522
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1523
|
+
self.ctx.temps.flush(out, indent)
|
|
1524
|
+
cond = f"{cond} && {guard_code}" if field_conds else guard_code
|
|
1525
|
+
if not field_conds and guard is None:
|
|
1526
|
+
# Always-matching class pattern -> else
|
|
1527
|
+
if i == 0:
|
|
1528
|
+
out.write(f"{indent}{{\n")
|
|
1529
|
+
else:
|
|
1530
|
+
out.write(f"{indent}}} else {{\n")
|
|
1531
|
+
else:
|
|
1532
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1533
|
+
# Emit field bindings
|
|
1534
|
+
self._gen_match_field_bindings(out, pattern, subject_expr, inner)
|
|
1535
|
+
self._emit_binding(out, as_name, as_raw, subject_expr, inner)
|
|
1536
|
+
self.ctx.indent_level += 2
|
|
1537
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1538
|
+
self.ctx.indent_level -= 2
|
|
1539
|
+
|
|
1540
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1541
|
+
if i == 0:
|
|
1542
|
+
out.write(f"{indent}{{\n")
|
|
1543
|
+
else:
|
|
1544
|
+
out.write(f"{indent}}} else {{\n")
|
|
1545
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1546
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, subject_expr, inner)
|
|
1547
|
+
self._emit_binding(out, as_name, as_raw, subject_expr, inner)
|
|
1548
|
+
self.ctx.indent_level += 2
|
|
1549
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1550
|
+
self.ctx.indent_level -= 2
|
|
1551
|
+
|
|
1552
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
1553
|
+
or_conds: list[str] = []
|
|
1554
|
+
for alt in pattern.patterns:
|
|
1555
|
+
if isinstance(alt, TpyClassPattern):
|
|
1556
|
+
fc = self._record_field_conditions(alt, subject_expr)
|
|
1557
|
+
or_conds.append("(" + " && ".join(fc) + ")" if fc else "true")
|
|
1558
|
+
elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1559
|
+
or_conds.clear()
|
|
1560
|
+
break
|
|
1561
|
+
else:
|
|
1562
|
+
raise CodeGenError(
|
|
1563
|
+
f"Unsupported or-pattern alt in Optional record: {type(alt).__name__}"
|
|
1564
|
+
)
|
|
1565
|
+
if or_conds:
|
|
1566
|
+
cond = " || ".join(or_conds)
|
|
1567
|
+
if guard is not None:
|
|
1568
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1569
|
+
self.ctx.temps.flush(out, indent)
|
|
1570
|
+
cond = f"({cond}) && {guard_code}"
|
|
1571
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1572
|
+
else:
|
|
1573
|
+
if i == 0:
|
|
1574
|
+
out.write(f"{indent}{{\n")
|
|
1575
|
+
else:
|
|
1576
|
+
out.write(f"{indent}}} else {{\n")
|
|
1577
|
+
self.ctx.indent_level += 2
|
|
1578
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1579
|
+
self.ctx.indent_level -= 2
|
|
1580
|
+
|
|
1581
|
+
else:
|
|
1582
|
+
raise CodeGenError(
|
|
1583
|
+
f"Unsupported pattern in Optional record dispatch: {type(pattern).__name__}"
|
|
1584
|
+
)
|
|
1585
|
+
|
|
1586
|
+
out.write(f"{indent}}}\n")
|
|
1587
|
+
|
|
1588
|
+
def _emit_optional_inner_if_elif(
|
|
1589
|
+
self, out: TextIO, cases: list['TpyMatchCase'], indent: str,
|
|
1590
|
+
) -> None:
|
|
1591
|
+
"""Emit if/elif chain for literal/value patterns on dereferenced Optional."""
|
|
1592
|
+
inner = INDENT * (self.ctx.indent_level + 2)
|
|
1593
|
+
deref = "__match_inner"
|
|
1594
|
+
|
|
1595
|
+
for i, case in enumerate(cases):
|
|
1596
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1597
|
+
keyword = "if" if i == 0 else "} else if"
|
|
1598
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1599
|
+
guard = case.guard
|
|
1600
|
+
|
|
1601
|
+
if isinstance(pattern, TpyLiteralPattern):
|
|
1602
|
+
cond = self._gen_literal_cond(pattern, deref)
|
|
1603
|
+
if guard is not None:
|
|
1604
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1605
|
+
self.ctx.temps.flush(out, indent)
|
|
1606
|
+
cond = f"{cond} && {guard_code}"
|
|
1607
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1608
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1609
|
+
self.ctx.indent_level += 2
|
|
1610
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1611
|
+
self.ctx.indent_level -= 2
|
|
1612
|
+
|
|
1613
|
+
elif isinstance(pattern, TpyValuePattern):
|
|
1614
|
+
val_code = self.expressions.gen_expr(pattern.expr)
|
|
1615
|
+
self.ctx.temps.flush(out, indent)
|
|
1616
|
+
cond = f"{deref} == {val_code}"
|
|
1617
|
+
if guard is not None:
|
|
1618
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1619
|
+
self.ctx.temps.flush(out, indent)
|
|
1620
|
+
cond = f"{cond} && {guard_code}"
|
|
1621
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1622
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1623
|
+
self.ctx.indent_level += 2
|
|
1624
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1625
|
+
self.ctx.indent_level -= 2
|
|
1626
|
+
|
|
1627
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1628
|
+
if i == 0:
|
|
1629
|
+
out.write(f"{indent}{{\n")
|
|
1630
|
+
else:
|
|
1631
|
+
out.write(f"{indent}}} else {{\n")
|
|
1632
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1633
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, deref, inner)
|
|
1634
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1635
|
+
self.ctx.indent_level += 2
|
|
1636
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1637
|
+
self.ctx.indent_level -= 2
|
|
1638
|
+
|
|
1639
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
1640
|
+
or_conds: list[str] = []
|
|
1641
|
+
for alt in pattern.patterns:
|
|
1642
|
+
if isinstance(alt, TpyLiteralPattern):
|
|
1643
|
+
or_conds.append(self._gen_literal_cond(alt, deref))
|
|
1644
|
+
elif isinstance(alt, TpyValuePattern):
|
|
1645
|
+
val_code = self.expressions.gen_expr(alt.expr)
|
|
1646
|
+
self.ctx.temps.flush(out, indent)
|
|
1647
|
+
or_conds.append(f"{deref} == {val_code}")
|
|
1648
|
+
elif isinstance(alt, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1649
|
+
or_conds.clear()
|
|
1650
|
+
break
|
|
1651
|
+
else:
|
|
1652
|
+
raise CodeGenError(
|
|
1653
|
+
f"Unsupported or-pattern alt in Optional inner: {type(alt).__name__}"
|
|
1654
|
+
)
|
|
1655
|
+
if or_conds:
|
|
1656
|
+
cond = " || ".join(or_conds)
|
|
1657
|
+
if guard is not None:
|
|
1658
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1659
|
+
self.ctx.temps.flush(out, indent)
|
|
1660
|
+
cond = f"({cond}) && {guard_code}"
|
|
1661
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1662
|
+
else:
|
|
1663
|
+
if i == 0:
|
|
1664
|
+
out.write(f"{indent}{{\n")
|
|
1665
|
+
else:
|
|
1666
|
+
out.write(f"{indent}}} else {{\n")
|
|
1667
|
+
self.ctx.indent_level += 2
|
|
1668
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1669
|
+
self.ctx.indent_level -= 2
|
|
1670
|
+
|
|
1671
|
+
else:
|
|
1672
|
+
raise CodeGenError(
|
|
1673
|
+
f"Unsupported pattern in Optional inner dispatch: {type(pattern).__name__}"
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
out.write(f"{indent}}}\n")
|
|
1677
|
+
|
|
1678
|
+
def _gen_literal_cond(
|
|
1679
|
+
self, pattern: 'TpyLiteralPattern', subject_expr: str = "__match_subject",
|
|
1680
|
+
) -> str:
|
|
1681
|
+
"""Generate a C++ comparison condition for a literal pattern."""
|
|
1682
|
+
val = pattern.value
|
|
1683
|
+
if isinstance(val, bool):
|
|
1684
|
+
return f"{subject_expr} == {'true' if val else 'false'}"
|
|
1685
|
+
elif isinstance(val, int):
|
|
1686
|
+
return f"{subject_expr} == {val}"
|
|
1687
|
+
elif isinstance(val, float):
|
|
1688
|
+
return f"{subject_expr} == {val!r}"
|
|
1689
|
+
elif isinstance(val, str):
|
|
1690
|
+
return f'{subject_expr} == {cpp_string_literal_expr(val)}'
|
|
1691
|
+
else:
|
|
1692
|
+
raise CodeGenError(f"Unsupported literal in match: {val!r}")
|
|
1693
|
+
|
|
1694
|
+
def _gen_match_if_elif_optional(
|
|
1695
|
+
self, out: TextIO, stmt: TpyMatch, subject_type: OptionalType, indent: str,
|
|
1696
|
+
) -> None:
|
|
1697
|
+
"""Generate match/case as if/elif chain for Optional subjects."""
|
|
1698
|
+
inner = INDENT * (self.ctx.indent_level + 1)
|
|
1699
|
+
uses_ptr = subject_type.uses_pointer_repr()
|
|
1700
|
+
# Determine how to check null and dereference
|
|
1701
|
+
null_cond = "__match_subject == nullptr" if uses_ptr else "!__match_subject.has_value()"
|
|
1702
|
+
has_val_cond = "__match_subject != nullptr" if uses_ptr else "__match_subject.has_value()"
|
|
1703
|
+
deref = "(*__match_subject)"
|
|
1704
|
+
|
|
1705
|
+
for i, case in enumerate(stmt.cases):
|
|
1706
|
+
self.ctx.emit_source_comment(out, case.loc, indent)
|
|
1707
|
+
keyword = "if" if i == 0 else "} else if"
|
|
1708
|
+
pattern, as_name, as_raw = self._unwrap_as_pattern(case.pattern)
|
|
1709
|
+
guard = case.guard
|
|
1710
|
+
|
|
1711
|
+
if isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
|
|
1712
|
+
# case None:
|
|
1713
|
+
cond = null_cond
|
|
1714
|
+
if guard is not None:
|
|
1715
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1716
|
+
self.ctx.temps.flush(out, indent)
|
|
1717
|
+
cond = f"{cond} && {guard_code}"
|
|
1718
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1719
|
+
self._emit_binding(out, as_name, as_raw, "__match_subject", inner)
|
|
1720
|
+
self.ctx.indent_level += 1
|
|
1721
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1722
|
+
self.ctx.indent_level -= 1
|
|
1723
|
+
|
|
1724
|
+
elif isinstance(pattern, TpyLiteralPattern):
|
|
1725
|
+
# Literal match on inner value (e.g. case 42: on Optional[Int32])
|
|
1726
|
+
lit_cond = self._gen_literal_cond(pattern, deref)
|
|
1727
|
+
cond = f"{has_val_cond} && {lit_cond}"
|
|
1728
|
+
if guard is not None:
|
|
1729
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1730
|
+
self.ctx.temps.flush(out, indent)
|
|
1731
|
+
cond = f"{cond} && {guard_code}"
|
|
1732
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1733
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1734
|
+
self.ctx.indent_level += 1
|
|
1735
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1736
|
+
self.ctx.indent_level -= 1
|
|
1737
|
+
|
|
1738
|
+
elif isinstance(pattern, TpyValuePattern):
|
|
1739
|
+
# Value pattern on inner type (e.g. case Color.RED: on Optional[Color])
|
|
1740
|
+
val_code = self.expressions.gen_expr(pattern.expr)
|
|
1741
|
+
self.ctx.temps.flush(out, indent)
|
|
1742
|
+
cond = f"{has_val_cond} && {deref} == {val_code}"
|
|
1743
|
+
if guard is not None:
|
|
1744
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1745
|
+
self.ctx.temps.flush(out, indent)
|
|
1746
|
+
cond = f"{cond} && {guard_code}"
|
|
1747
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1748
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1749
|
+
self.ctx.indent_level += 1
|
|
1750
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1751
|
+
self.ctx.indent_level -= 1
|
|
1752
|
+
|
|
1753
|
+
elif isinstance(pattern, TpyClassPattern):
|
|
1754
|
+
# Class pattern on inner record type (e.g. case Point(x=0): on Optional[Point])
|
|
1755
|
+
field_conds = self._record_field_conditions(pattern, deref)
|
|
1756
|
+
cond_parts = [has_val_cond] + field_conds
|
|
1757
|
+
cond = " && ".join(cond_parts)
|
|
1758
|
+
if guard is not None:
|
|
1759
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1760
|
+
self.ctx.temps.flush(out, indent)
|
|
1761
|
+
cond = f"{cond} && {guard_code}"
|
|
1762
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1763
|
+
# Emit field bindings using dereferenced subject
|
|
1764
|
+
self._gen_match_field_bindings(out, pattern, deref, inner)
|
|
1765
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1766
|
+
self.ctx.indent_level += 1
|
|
1767
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1768
|
+
self.ctx.indent_level -= 1
|
|
1769
|
+
|
|
1770
|
+
elif isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1771
|
+
needs_deref = (
|
|
1772
|
+
isinstance(pattern, TpyCapturePattern) or as_name is not None
|
|
1773
|
+
)
|
|
1774
|
+
if isinstance(pattern, TpyCapturePattern) and guard is not None:
|
|
1775
|
+
# Guarded capture: condition on has_value + guard
|
|
1776
|
+
cond = f"{has_val_cond} && {self.expressions.gen_expr(guard)}"
|
|
1777
|
+
self.ctx.temps.flush(out, indent)
|
|
1778
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1779
|
+
elif guard is not None:
|
|
1780
|
+
# Guarded wildcard (no capture)
|
|
1781
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1782
|
+
self.ctx.temps.flush(out, indent)
|
|
1783
|
+
out.write(f"{indent}{keyword} ({guard_code}) {{\n")
|
|
1784
|
+
elif needs_deref:
|
|
1785
|
+
# Unguarded capture: guard on has_value to avoid UB
|
|
1786
|
+
out.write(f"{indent}{keyword} ({has_val_cond}) {{\n")
|
|
1787
|
+
elif i == 0:
|
|
1788
|
+
out.write(f"{indent}{{\n")
|
|
1789
|
+
else:
|
|
1790
|
+
out.write(f"{indent}}} else {{\n")
|
|
1791
|
+
if isinstance(pattern, TpyCapturePattern):
|
|
1792
|
+
self._emit_binding(out, escape_cpp_name(pattern.name), pattern.name, deref, inner)
|
|
1793
|
+
self._emit_binding(out, as_name, as_raw, deref, inner)
|
|
1794
|
+
self.ctx.indent_level += 1
|
|
1795
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1796
|
+
self.ctx.indent_level -= 1
|
|
1797
|
+
|
|
1798
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
1799
|
+
# OR of None/literal/value/class conditions
|
|
1800
|
+
or_conds: list[str] = []
|
|
1801
|
+
for alt in pattern.patterns:
|
|
1802
|
+
if isinstance(alt, TpyLiteralPattern) and alt.value is None:
|
|
1803
|
+
or_conds.append(null_cond)
|
|
1804
|
+
elif isinstance(alt, TpyLiteralPattern):
|
|
1805
|
+
lit_c = self._gen_literal_cond(alt, deref)
|
|
1806
|
+
or_conds.append(f"({has_val_cond} && {lit_c})")
|
|
1807
|
+
elif isinstance(alt, TpyValuePattern):
|
|
1808
|
+
val_code = self.expressions.gen_expr(alt.expr)
|
|
1809
|
+
self.ctx.temps.flush(out, indent)
|
|
1810
|
+
or_conds.append(f"({has_val_cond} && {deref} == {val_code})")
|
|
1811
|
+
elif isinstance(alt, TpyClassPattern):
|
|
1812
|
+
fc = self._record_field_conditions(alt, deref)
|
|
1813
|
+
parts = [has_val_cond] + fc
|
|
1814
|
+
or_conds.append("(" + " && ".join(parts) + ")")
|
|
1815
|
+
elif isinstance(alt, TpyWildcardPattern):
|
|
1816
|
+
or_conds.clear()
|
|
1817
|
+
break
|
|
1818
|
+
else:
|
|
1819
|
+
raise CodeGenError(
|
|
1820
|
+
f"Unsupported or-pattern alt for Optional: {type(alt).__name__}"
|
|
1821
|
+
)
|
|
1822
|
+
if or_conds:
|
|
1823
|
+
cond = " || ".join(or_conds)
|
|
1824
|
+
if guard is not None:
|
|
1825
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1826
|
+
self.ctx.temps.flush(out, indent)
|
|
1827
|
+
cond = f"({cond}) && {guard_code}"
|
|
1828
|
+
out.write(f"{indent}{keyword} ({cond}) {{\n")
|
|
1829
|
+
else:
|
|
1830
|
+
if guard is not None:
|
|
1831
|
+
guard_code = self.expressions.gen_expr(guard)
|
|
1832
|
+
self.ctx.temps.flush(out, indent)
|
|
1833
|
+
out.write(f"{indent}{keyword} ({guard_code}) {{\n")
|
|
1834
|
+
elif i == 0:
|
|
1835
|
+
out.write(f"{indent}{{\n")
|
|
1836
|
+
else:
|
|
1837
|
+
out.write(f"{indent}}} else {{\n")
|
|
1838
|
+
self.ctx.indent_level += 1
|
|
1839
|
+
self._emit_case_body(out, case.body, case.type_facts)
|
|
1840
|
+
self.ctx.indent_level -= 1
|
|
1841
|
+
|
|
1842
|
+
else:
|
|
1843
|
+
raise CodeGenError(
|
|
1844
|
+
f"Unsupported match pattern for Optional: {type(pattern).__name__}"
|
|
1845
|
+
)
|
|
1846
|
+
|
|
1847
|
+
out.write(f"{indent}}}\n")
|
|
1848
|
+
|
|
1849
|
+
def _record_field_conditions(
|
|
1850
|
+
self, pattern: 'TpyClassPattern', subject_expr: str = "__match_subject",
|
|
1851
|
+
) -> list[str]:
|
|
1852
|
+
"""Generate C++ field comparison conditions for a record class pattern."""
|
|
1853
|
+
conds: list[str] = []
|
|
1854
|
+
for field_name, sub_pattern in pattern.keywords:
|
|
1855
|
+
inner = sub_pattern
|
|
1856
|
+
if isinstance(inner, TpyAsPattern):
|
|
1857
|
+
inner = inner.pattern
|
|
1858
|
+
if isinstance(inner, TpyLiteralPattern):
|
|
1859
|
+
val = inner.value
|
|
1860
|
+
if isinstance(val, bool):
|
|
1861
|
+
conds.append(f"{subject_expr}.{field_name} == {'true' if val else 'false'}")
|
|
1862
|
+
elif isinstance(val, int):
|
|
1863
|
+
conds.append(f"{subject_expr}.{field_name} == {val}")
|
|
1864
|
+
elif isinstance(val, float):
|
|
1865
|
+
conds.append(f"{subject_expr}.{field_name} == {val!r}")
|
|
1866
|
+
elif isinstance(val, str):
|
|
1867
|
+
conds.append(f'{subject_expr}.{field_name} == {cpp_string_literal_expr(val)}')
|
|
1868
|
+
elif isinstance(inner, TpyClassPattern) and inner.is_union_field_guard:
|
|
1869
|
+
# Union-typed field: runtime holds_alternative check
|
|
1870
|
+
assert inner.resolved_type is not None
|
|
1871
|
+
cpp_type = self.types.type_to_cpp(inner.resolved_type)
|
|
1872
|
+
conds.append(f"std::holds_alternative<{cpp_type}>({subject_expr}.{field_name})")
|
|
1873
|
+
# Recurse for nested field conditions on the variant member
|
|
1874
|
+
if inner.keywords:
|
|
1875
|
+
get_expr = f"std::get<{cpp_type}>({subject_expr}.{field_name})"
|
|
1876
|
+
nested = self._record_field_conditions(inner, get_expr)
|
|
1877
|
+
conds.extend(nested)
|
|
1878
|
+
elif isinstance(inner, TpyClassPattern) and not inner.is_union_field_guard and inner.keywords:
|
|
1879
|
+
# Non-union record field with nested sub-patterns: recurse through it
|
|
1880
|
+
nested = self._record_field_conditions(inner, f"{subject_expr}.{field_name}")
|
|
1881
|
+
conds.extend(nested)
|
|
1882
|
+
return conds
|
|
1883
|
+
|
|
1884
|
+
@staticmethod
|
|
1885
|
+
def _sub_has_union_field_guard(sub: TpyPattern) -> bool:
|
|
1886
|
+
"""Check if a field sub-pattern contains a union field guard."""
|
|
1887
|
+
inner = sub
|
|
1888
|
+
if isinstance(inner, TpyAsPattern):
|
|
1889
|
+
inner = inner.pattern
|
|
1890
|
+
return isinstance(inner, TpyClassPattern) and inner.is_union_field_guard
|
|
1891
|
+
|
|
1892
|
+
def _has_shared_variant_index(self, stmt: TpyMatch, subject_type: UnionType) -> bool:
|
|
1893
|
+
"""Check if multiple cases resolve to the same variant index (e.g. union field guards)."""
|
|
1894
|
+
seen: set[int] = set()
|
|
1895
|
+
for case in stmt.cases:
|
|
1896
|
+
pat = case.pattern
|
|
1897
|
+
if isinstance(pat, TpyAsPattern):
|
|
1898
|
+
pat = pat.pattern
|
|
1899
|
+
indices: list[int] = []
|
|
1900
|
+
if isinstance(pat, TpyClassPattern) and pat.resolved_type is not None:
|
|
1901
|
+
indices.append(self._variant_index(subject_type, pat.resolved_type))
|
|
1902
|
+
elif isinstance(pat, TpyOrPattern):
|
|
1903
|
+
for alt in pat.patterns:
|
|
1904
|
+
if isinstance(alt, TpyClassPattern) and alt.resolved_type is not None:
|
|
1905
|
+
indices.append(self._variant_index(subject_type, alt.resolved_type))
|
|
1906
|
+
for idx in indices:
|
|
1907
|
+
if idx in seen:
|
|
1908
|
+
return True
|
|
1909
|
+
seen.add(idx)
|
|
1910
|
+
return False
|
|
1911
|
+
|
|
1912
|
+
def _variant_index(self, union_type: UnionType, member_type: TpyType) -> int:
|
|
1913
|
+
"""Find the index of a member type in a union's canonical member ordering.
|
|
1914
|
+
|
|
1915
|
+
For recursive unions (including narrowed-by-None subsets), indices are
|
|
1916
|
+
resolved against the alias's full member tuple so they match the
|
|
1917
|
+
wrapper struct's variant ordering.
|
|
1918
|
+
"""
|
|
1919
|
+
wrapper = union_type.wrapper_info()
|
|
1920
|
+
members = wrapper.full_members if wrapper is not None else union_type.members
|
|
1921
|
+
for i, m in enumerate(members):
|
|
1922
|
+
if m == member_type:
|
|
1923
|
+
return i
|
|
1924
|
+
raise CodeGenError(f"type '{member_type}' not found in union '{union_type}'")
|
|
1925
|
+
|
|
1926
|
+
def _switch_literal_label(self, pattern: TpyLiteralPattern) -> str:
|
|
1927
|
+
"""Generate a C++ case label for a literal pattern (int or bool)."""
|
|
1928
|
+
val = pattern.value
|
|
1929
|
+
if isinstance(val, bool):
|
|
1930
|
+
return "true" if val else "false"
|
|
1931
|
+
elif isinstance(val, int):
|
|
1932
|
+
return str(val)
|
|
1933
|
+
else:
|
|
1934
|
+
raise CodeGenError(f"Cannot use literal {val!r} in switch case label")
|
|
1935
|
+
|
|
1936
|
+
def _record_capture_type(
|
|
1937
|
+
self, pattern: TpyClassPattern, field_name: str, capture_name: str,
|
|
1938
|
+
) -> None:
|
|
1939
|
+
"""Register the field's declared type for a match-bound capture so
|
|
1940
|
+
``_get_cpp_declared_type`` (which only looks at ``var_types``) can
|
|
1941
|
+
drive narrowing-aware deref on the captured name."""
|
|
1942
|
+
record_type = pattern.resolved_type
|
|
1943
|
+
if not isinstance(record_type, NominalType):
|
|
1944
|
+
return
|
|
1945
|
+
record = self.ctx.analyzer.registry.get_record_for_type(record_type)
|
|
1946
|
+
for f in record.fields:
|
|
1947
|
+
if f.name == field_name:
|
|
1948
|
+
self.ctx.var_types[capture_name] = f.type
|
|
1949
|
+
return
|
|
1950
|
+
|
|
1951
|
+
def _gen_match_field_bindings(
|
|
1952
|
+
self, out: TextIO, pattern: TpyClassPattern, case_var: str, indent: str,
|
|
1953
|
+
) -> None:
|
|
1954
|
+
"""Emit local variable bindings for class pattern keyword fields."""
|
|
1955
|
+
for field_name, sub_pattern in pattern.keywords:
|
|
1956
|
+
if isinstance(sub_pattern, TpyCapturePattern):
|
|
1957
|
+
self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
|
|
1958
|
+
self._record_capture_type(pattern, field_name, sub_pattern.name)
|
|
1959
|
+
elif isinstance(sub_pattern, TpyWildcardPattern):
|
|
1960
|
+
pass
|
|
1961
|
+
elif isinstance(sub_pattern, TpyLiteralPattern):
|
|
1962
|
+
pass # Literal sub-patterns handled as conditions (future)
|
|
1963
|
+
elif isinstance(sub_pattern, TpyClassPattern):
|
|
1964
|
+
if sub_pattern.is_union_field_guard and sub_pattern.keywords:
|
|
1965
|
+
# Union field with nested record patterns: extract variant, bind sub-fields
|
|
1966
|
+
cpp_type = self.types.type_to_cpp(sub_pattern.resolved_type)
|
|
1967
|
+
# Include parent var in temp name to avoid collisions when
|
|
1968
|
+
# sibling fields share a sub-field name
|
|
1969
|
+
parent_sfx = case_var.replace(".", "_").replace("*", "").lstrip("_")
|
|
1970
|
+
temp = f"__field_{parent_sfx}_{field_name}"
|
|
1971
|
+
out.write(f"{indent}auto& {temp} = std::get<{cpp_type}>({case_var}.{field_name});\n")
|
|
1972
|
+
self._gen_match_field_bindings(out, sub_pattern, temp, indent)
|
|
1973
|
+
elif not sub_pattern.is_union_field_guard and sub_pattern.keywords:
|
|
1974
|
+
# Compile-time type guard with nested field bindings: recurse with direct access
|
|
1975
|
+
self._gen_match_field_bindings(out, sub_pattern, f"{case_var}.{field_name}", indent)
|
|
1976
|
+
# else: type guard only (no nested bindings), no output needed
|
|
1977
|
+
elif isinstance(sub_pattern, TpyAsPattern):
|
|
1978
|
+
inner_sub = sub_pattern.pattern
|
|
1979
|
+
if isinstance(inner_sub, TpyClassPattern):
|
|
1980
|
+
# Type pattern with capture (e.g., value=str() as v)
|
|
1981
|
+
if inner_sub.is_union_field_guard:
|
|
1982
|
+
cpp_type = self.types.type_to_cpp(inner_sub.resolved_type)
|
|
1983
|
+
field_expr = f"std::get<{cpp_type}>({case_var}.{field_name})"
|
|
1984
|
+
else:
|
|
1985
|
+
field_expr = f"{case_var}.{field_name}"
|
|
1986
|
+
self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, field_expr, indent)
|
|
1987
|
+
# Recurse for nested field bindings
|
|
1988
|
+
if inner_sub.keywords:
|
|
1989
|
+
self._gen_match_field_bindings(out, inner_sub, escape_cpp_name(sub_pattern.name), indent)
|
|
1990
|
+
elif isinstance(inner_sub, TpyLiteralPattern):
|
|
1991
|
+
self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
|
|
1992
|
+
elif isinstance(inner_sub, (TpyWildcardPattern, TpyCapturePattern)):
|
|
1993
|
+
if isinstance(inner_sub, TpyCapturePattern):
|
|
1994
|
+
self._emit_binding(out, escape_cpp_name(inner_sub.name), inner_sub.name, f"{case_var}.{field_name}", indent)
|
|
1995
|
+
self._record_capture_type(pattern, field_name, inner_sub.name)
|
|
1996
|
+
self._emit_binding(out, escape_cpp_name(sub_pattern.name), sub_pattern.name, f"{case_var}.{field_name}", indent)
|
|
1997
|
+
self._record_capture_type(pattern, field_name, sub_pattern.name)
|