tpy-lang 0.3.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
- tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
- tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
- tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
- tpyc/__init__.py +104 -0
- tpyc/__main__.py +6 -0
- tpyc/_buildinfo.py +1 -0
- tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
- tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
- tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
- tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
- tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
- tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
- tpyc/_data/lib/tpy/_functools_macros.py +80 -0
- tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
- tpyc/_data/lib/tpy/argparse.py +2062 -0
- tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
- tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
- tpyc/_data/lib/tpy/base64.py +410 -0
- tpyc/_data/lib/tpy/bisect.py +39 -0
- tpyc/_data/lib/tpy/builtins.py +38 -0
- tpyc/_data/lib/tpy/dataclasses.py +354 -0
- tpyc/_data/lib/tpy/enum.py +23 -0
- tpyc/_data/lib/tpy/functools.py +33 -0
- tpyc/_data/lib/tpy/hashlib.py +206 -0
- tpyc/_data/lib/tpy/heapq.py +118 -0
- tpyc/_data/lib/tpy/io.py +395 -0
- tpyc/_data/lib/tpy/json.py +221 -0
- tpyc/_data/lib/tpy/math.py +406 -0
- tpyc/_data/lib/tpy/random.py +597 -0
- tpyc/_data/lib/tpy/re.py +467 -0
- tpyc/_data/lib/tpy/socket.py +379 -0
- tpyc/_data/lib/tpy/struct.py +178 -0
- tpyc/_data/lib/tpy/sys.py +40 -0
- tpyc/_data/lib/tpy/time.py +39 -0
- tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
- tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
- tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
- tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
- tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
- tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
- tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
- tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
- tpyc/_data/lib/tpy/tpy/_version.py +29 -0
- tpyc/_data/lib/tpy/tpy/bits.py +28 -0
- tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
- tpyc/_data/lib/tpy/tpy/extern.py +8 -0
- tpyc/_data/lib/tpy/tpy/mem.py +49 -0
- tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
- tpyc/_data/lib/tpy/tpy/version.py +21 -0
- tpyc/_data/lib/tpy/typing.py +13 -0
- tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
- tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
- tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
- tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
- tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
- tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
- tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
- tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
- tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
- tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
- tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
- tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
- tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
- tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
- tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
- tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
- tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
- tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
- tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
- tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
- tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
- tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
- tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
- tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
- tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
- tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
- tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
- tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
- tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
- tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
- tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
- tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
- tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
- tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
- tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
- tpyc/_data/runtime/cpp/third_party/README.md +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
- tpyc/build/__init__.py +7 -0
- tpyc/build/pcre2.py +122 -0
- tpyc/build/third_party.py +413 -0
- tpyc/cli.py +822 -0
- tpyc/codegen_cpp/__init__.py +18 -0
- tpyc/codegen_cpp/builtins.py +484 -0
- tpyc/codegen_cpp/context.py +2064 -0
- tpyc/codegen_cpp/expressions.py +5940 -0
- tpyc/codegen_cpp/functions.py +1913 -0
- tpyc/codegen_cpp/gen_async.py +3258 -0
- tpyc/codegen_cpp/gen_generators.py +657 -0
- tpyc/codegen_cpp/generator.py +2258 -0
- tpyc/codegen_cpp/match.py +1997 -0
- tpyc/codegen_cpp/param_const.py +172 -0
- tpyc/codegen_cpp/protocols.py +907 -0
- tpyc/codegen_cpp/records.py +1654 -0
- tpyc/codegen_cpp/resumable_cfg.py +1651 -0
- tpyc/codegen_cpp/statements.py +4963 -0
- tpyc/codegen_cpp/string_dispatch.py +76 -0
- tpyc/codegen_cpp/test_context.py +46 -0
- tpyc/codegen_cpp/test_param_const.py +113 -0
- tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
- tpyc/codegen_cpp/type_resolution.py +53 -0
- tpyc/codegen_cpp/types.py +436 -0
- tpyc/codegen_cpp/variant_access.py +135 -0
- tpyc/coercions.py +749 -0
- tpyc/compilation_context.py +57 -0
- tpyc/compiler.py +3945 -0
- tpyc/cycle_detection.py +358 -0
- tpyc/diagnostics.py +135 -0
- tpyc/dump_types.py +353 -0
- tpyc/frontend_diagnostics.py +47 -0
- tpyc/frontend_ir/__init__.py +140 -0
- tpyc/frontend_ir/lower.py +1098 -0
- tpyc/frontend_ir/nodes.py +718 -0
- tpyc/frontend_ir/resolver_adapter.py +151 -0
- tpyc/frontend_plugin.py +209 -0
- tpyc/install_docs.py +81 -0
- tpyc/liveness.py +756 -0
- tpyc/macro_api.py +1724 -0
- tpyc/macro_loader.py +497 -0
- tpyc/module_names.py +64 -0
- tpyc/modules/__init__.py +31 -0
- tpyc/modules/defs.py +89 -0
- tpyc/modules/registry.py +36 -0
- tpyc/modules/resolver.py +192 -0
- tpyc/modules/type_resolution.py +629 -0
- tpyc/namespace.py +172 -0
- tpyc/parse/__init__.py +84 -0
- tpyc/parse/imports.py +490 -0
- tpyc/parse/nodes.py +1732 -0
- tpyc/parse/parser.py +4043 -0
- tpyc/parse/resolve_refs.py +466 -0
- tpyc/parse/type_resolver.py +1060 -0
- tpyc/prescan.py +254 -0
- tpyc/qnames.py +149 -0
- tpyc/repl.py +529 -0
- tpyc/repl_backends.py +848 -0
- tpyc/sema/__init__.py +21 -0
- tpyc/sema/analyzer.py +3625 -0
- tpyc/sema/bound_check.py +72 -0
- tpyc/sema/builder_trace.py +684 -0
- tpyc/sema/calls.py +5406 -0
- tpyc/sema/compatibility.py +2107 -0
- tpyc/sema/context.py +1243 -0
- tpyc/sema/expressions.py +3737 -0
- tpyc/sema/flow_facts.py +199 -0
- tpyc/sema/init_tracker.py +150 -0
- tpyc/sema/list_literals.py +69 -0
- tpyc/sema/literal_utils.py +27 -0
- tpyc/sema/local_deduction.py +1088 -0
- tpyc/sema/macros.py +179 -0
- tpyc/sema/match.py +1177 -0
- tpyc/sema/method_expansion.py +347 -0
- tpyc/sema/methods.py +2197 -0
- tpyc/sema/mutation_propagation.py +268 -0
- tpyc/sema/narrowing.py +857 -0
- tpyc/sema/numeric_lattice.py +160 -0
- tpyc/sema/operators.py +402 -0
- tpyc/sema/overloads.py +841 -0
- tpyc/sema/protocols.py +1209 -0
- tpyc/sema/reach_analysis.py +202 -0
- tpyc/sema/registration.py +3156 -0
- tpyc/sema/scope_tracker.py +193 -0
- tpyc/sema/statements.py +4426 -0
- tpyc/sema/type_ops.py +1879 -0
- tpyc/sema/value_range.py +181 -0
- tpyc/symbol_binding.py +259 -0
- tpyc/test_c3_mro.py +208 -0
- tpyc/test_cli_argv.py +52 -0
- tpyc/test_compiler.py +559 -0
- tpyc/test_contains_type_param.py +101 -0
- tpyc/test_cycle_detection.py +221 -0
- tpyc/test_dump_types.py +225 -0
- tpyc/test_install_docs.py +65 -0
- tpyc/test_local_cpp_form.py +135 -0
- tpyc/test_macro_loader.py +76 -0
- tpyc/test_method_expansion.py +254 -0
- tpyc/test_nominal_identity.py +182 -0
- tpyc/test_overloads.py +410 -0
- tpyc/test_parse.py +303 -0
- tpyc/test_parse_type_ref.py +506 -0
- tpyc/test_parse_version_info.py +58 -0
- tpyc/test_reach_analysis.py +72 -0
- tpyc/test_ref_type.py +216 -0
- tpyc/test_send_sync_substitution.py +276 -0
- tpyc/test_tuple_mutation_propagation.py +206 -0
- tpyc/test_type_def_registry.py +1729 -0
- tpyc/test_union_types.py +195 -0
- tpyc/type_def_registry.py +975 -0
- tpyc/typesys.py +5104 -0
tpyc/sema/match.py
ADDED
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Match/Case Semantic Analysis
|
|
3
|
+
|
|
4
|
+
Semantic analysis for match/case statements and pattern matching.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from ..typesys import (
|
|
12
|
+
TpyType,
|
|
13
|
+
NominalType, AliasRef, RecursiveAliasInstanceType,
|
|
14
|
+
NoneType, OptionalType, UnionType, PendingStrType,
|
|
15
|
+
LiteralType, LiteralValue, LiteralTag, TypeParamRef,
|
|
16
|
+
unwrap_readonly, unwrap_ref_type, make_union,
|
|
17
|
+
is_float_type, is_any_str_type,
|
|
18
|
+
)
|
|
19
|
+
from ..modules import _resolve_concrete_type_name
|
|
20
|
+
from .flow_facts import FlowFacts
|
|
21
|
+
from ..type_def_registry import (
|
|
22
|
+
is_bool_type, is_fixed_int_type, is_big_int_type,
|
|
23
|
+
is_str_category, is_char_type, is_float_category,
|
|
24
|
+
is_enum_type, enum_info_of,
|
|
25
|
+
)
|
|
26
|
+
from ..parse import (
|
|
27
|
+
TpyName, TpyFieldAccess,
|
|
28
|
+
TpyMatch, TpyMatchCase, TpyPattern, TpyWildcardPattern, TpyCapturePattern,
|
|
29
|
+
TpyClassPattern, TpyLiteralPattern, TpyValuePattern, TpyOrPattern, TpyAsPattern,
|
|
30
|
+
)
|
|
31
|
+
from ..parse.nodes import stmt_has_any_suspension
|
|
32
|
+
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from ..typesys import RecordInfo
|
|
35
|
+
from .context import SemanticContext
|
|
36
|
+
from .expressions import ExpressionAnalyzer
|
|
37
|
+
from .statements import StatementAnalyzer
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _subst_type_params(typ: TpyType, subst: dict[str, TpyType]) -> TpyType:
|
|
41
|
+
"""Substitute TypeParamRef instances in a type according to subst map."""
|
|
42
|
+
if isinstance(typ, TypeParamRef) and typ.name in subst:
|
|
43
|
+
return subst[typ.name]
|
|
44
|
+
return typ.map_inner_types(lambda t: _subst_type_params(t, subst))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OrPatternKind(Enum):
|
|
48
|
+
UNION = "union"
|
|
49
|
+
NONUNION = "nonunion"
|
|
50
|
+
RECORD = "record"
|
|
51
|
+
OPTIONAL = "optional"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MatchAnalyzer:
|
|
55
|
+
"""Semantic analysis for match/case statements."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, ctx: SemanticContext, stmts: StatementAnalyzer,
|
|
58
|
+
expr: ExpressionAnalyzer):
|
|
59
|
+
self.ctx = ctx
|
|
60
|
+
self.stmts = stmts
|
|
61
|
+
self.expr = expr
|
|
62
|
+
|
|
63
|
+
def analyze_match(self, stmt: TpyMatch) -> None:
|
|
64
|
+
"""Analyze a match/case statement."""
|
|
65
|
+
subject_type = self.expr.analyze_expr(stmt.subject)
|
|
66
|
+
effective_type = unwrap_ref_type(unwrap_readonly(subject_type))
|
|
67
|
+
# Expand recursive union alias placeholder to its underlying
|
|
68
|
+
# UnionType so match dispatch sees the variant arms.
|
|
69
|
+
if isinstance(effective_type, AliasRef):
|
|
70
|
+
alias = self.ctx.registry.get_type_alias(effective_type.name)
|
|
71
|
+
if alias is not None:
|
|
72
|
+
effective_type = alias
|
|
73
|
+
stmt.subject_type = effective_type
|
|
74
|
+
# A generic recursive alias instance (Tree[int]) stays the codegen
|
|
75
|
+
# subject -- its wrapper_info() drives .value variant dispatch. For the
|
|
76
|
+
# (UnionType-centric) pattern arm-analysis below, stand in a synthesized
|
|
77
|
+
# union of its substituted alternatives so the existing union path
|
|
78
|
+
# applies unchanged.
|
|
79
|
+
if isinstance(effective_type, RecursiveAliasInstanceType):
|
|
80
|
+
effective_type = make_union(*effective_type.alternatives())
|
|
81
|
+
is_union = isinstance(effective_type, UnionType)
|
|
82
|
+
is_enum = is_enum_type(effective_type)
|
|
83
|
+
is_literal = isinstance(effective_type, LiteralType)
|
|
84
|
+
is_primitive = is_literal or (
|
|
85
|
+
is_fixed_int_type(effective_type) or is_big_int_type(effective_type)
|
|
86
|
+
or is_float_category(effective_type) or is_bool_type(effective_type)
|
|
87
|
+
or is_str_category(effective_type) or is_char_type(effective_type)
|
|
88
|
+
or isinstance(effective_type, PendingStrType)
|
|
89
|
+
)
|
|
90
|
+
is_record = (
|
|
91
|
+
isinstance(effective_type, NominalType)
|
|
92
|
+
and effective_type.is_user_record
|
|
93
|
+
and self.ctx.registry.get_record(effective_type.name) is not None
|
|
94
|
+
)
|
|
95
|
+
is_optional = isinstance(effective_type, OptionalType)
|
|
96
|
+
if not (is_union or is_enum or is_primitive or is_record or is_optional):
|
|
97
|
+
raise self.ctx.error(
|
|
98
|
+
f"match subject must be a union, enum, primitive, record, "
|
|
99
|
+
f"or Optional type, got '{effective_type}'", stmt
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Subject variable name for narrowing (only if simple name)
|
|
103
|
+
subject_name: str | None = None
|
|
104
|
+
if isinstance(stmt.subject, TpyName):
|
|
105
|
+
subject_name = stmt.subject.name
|
|
106
|
+
|
|
107
|
+
# Resumable frame (H1): when a generator/async `match` carries a
|
|
108
|
+
# suspension, its arm bodies become separate states, so pattern
|
|
109
|
+
# bindings must be frame fields (see the per-arm binding loop).
|
|
110
|
+
# `current_function` may be a module-init sentinel for a top-level
|
|
111
|
+
# `match` (no `is_generator`/`is_async`); a suspension cannot appear
|
|
112
|
+
# at module scope anyway, so guard with getattr.
|
|
113
|
+
cur_fn = self.ctx.func.current_function
|
|
114
|
+
needs_frame_field = (
|
|
115
|
+
(getattr(cur_fn, "is_generator", False)
|
|
116
|
+
or getattr(cur_fn, "is_async", False))
|
|
117
|
+
and stmt_has_any_suspension(stmt)
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
had_wildcard = False
|
|
121
|
+
seen_types: set[str] = set()
|
|
122
|
+
seen_values: set[object] = set()
|
|
123
|
+
|
|
124
|
+
scope_before = set(self.ctx.func.current_scope.bindings.keys())
|
|
125
|
+
assigned_before = frozenset(self.ctx.func.definitely_assigned)
|
|
126
|
+
bindings_before = dict(self.ctx.func.current_scope.bindings)
|
|
127
|
+
ns_types_before = self.stmts._save_ns_var_types()
|
|
128
|
+
before = self.stmts.init.save()
|
|
129
|
+
|
|
130
|
+
arm_states: list[FlowFacts] = []
|
|
131
|
+
arm_bindings: list[dict[str, TpyType]] = []
|
|
132
|
+
consumed_before = self.ctx.func.current_consumed_own_params.copy()
|
|
133
|
+
arm_consumed: list[tuple[set[str], bool]] = [] # (consumed_set, terminated)
|
|
134
|
+
|
|
135
|
+
for case in stmt.cases:
|
|
136
|
+
if had_wildcard:
|
|
137
|
+
raise self.ctx.error(
|
|
138
|
+
"unreachable case after wildcard pattern", case.pattern
|
|
139
|
+
)
|
|
140
|
+
# Restore state to pre-match for each arm
|
|
141
|
+
self.stmts.init.restore(before)
|
|
142
|
+
self.ctx.func.current_consumed_own_params = consumed_before.copy()
|
|
143
|
+
self.ctx.func.current_scope.bindings = dict(bindings_before)
|
|
144
|
+
self.stmts._restore_ns_var_types(ns_types_before)
|
|
145
|
+
|
|
146
|
+
pattern_bindings: dict[str, TpyType] = {}
|
|
147
|
+
# Guarded cases don't consume types/values for duplicate detection,
|
|
148
|
+
# because the guard may fail and fall through.
|
|
149
|
+
has_guard = case.guard is not None
|
|
150
|
+
saved_seen_types = set(seen_types) if has_guard else None
|
|
151
|
+
saved_seen_values = set(seen_values) if has_guard else None
|
|
152
|
+
if is_union:
|
|
153
|
+
self._analyze_pattern(case.pattern, effective_type, seen_types, pattern_bindings, stmt)
|
|
154
|
+
elif is_record:
|
|
155
|
+
self._analyze_pattern_record(
|
|
156
|
+
case.pattern, effective_type, pattern_bindings, stmt,
|
|
157
|
+
)
|
|
158
|
+
elif is_optional:
|
|
159
|
+
self._analyze_pattern_optional(
|
|
160
|
+
case.pattern, effective_type, seen_values, pattern_bindings, stmt,
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
self._analyze_pattern_nonunion(
|
|
164
|
+
case.pattern, effective_type, seen_values, pattern_bindings, stmt,
|
|
165
|
+
)
|
|
166
|
+
if has_guard:
|
|
167
|
+
seen_types.clear()
|
|
168
|
+
seen_types.update(saved_seen_types) # type: ignore[arg-type]
|
|
169
|
+
seen_values.clear()
|
|
170
|
+
seen_values.update(saved_seen_values) # type: ignore[arg-type]
|
|
171
|
+
|
|
172
|
+
for name, ty in pattern_bindings.items():
|
|
173
|
+
self.ctx.func.current_scope.define(name, ty)
|
|
174
|
+
self.stmts.init.mark_assigned(name)
|
|
175
|
+
if name not in self.ctx.func.var_scope_depth:
|
|
176
|
+
self.ctx.func.var_scope_depth[name] = self.ctx.func.current_scope.depth
|
|
177
|
+
# Resumable frame (H1): a `match` carrying a suspension
|
|
178
|
+
# decomposes into per-arm body states, so a pattern binding
|
|
179
|
+
# read in an arm body must live in the frame rather than as a
|
|
180
|
+
# dispatch-local that vanishes at the state split. Register it
|
|
181
|
+
# in the function namespace so `_analyze_function` collects it
|
|
182
|
+
# into `generator_locals` (-> a struct field). Pattern
|
|
183
|
+
# bindings otherwise only land in `current_scope`, which the
|
|
184
|
+
# generator-local collection does not read.
|
|
185
|
+
if needs_frame_field and self.ctx.func.current_ns is not None:
|
|
186
|
+
self.ctx.func.current_ns.bind_variable(name, ty)
|
|
187
|
+
|
|
188
|
+
# Narrow subject variable for class patterns (union only)
|
|
189
|
+
narrowing_facts = self._match_case_narrowing_facts(
|
|
190
|
+
case.pattern, subject_name, effective_type,
|
|
191
|
+
) if is_union else {}
|
|
192
|
+
if narrowing_facts:
|
|
193
|
+
case.type_facts = self.stmts._filter_union_codegen_facts(narrowing_facts)
|
|
194
|
+
self.ctx.func.narrowed_types.update(narrowing_facts)
|
|
195
|
+
|
|
196
|
+
# Narrow Optional subject to inner type in non-None arms
|
|
197
|
+
if is_optional and subject_name is not None:
|
|
198
|
+
pat = case.pattern
|
|
199
|
+
if isinstance(pat, TpyAsPattern):
|
|
200
|
+
pat = pat.pattern
|
|
201
|
+
is_none_arm = isinstance(pat, TpyLiteralPattern) and pat.value is None
|
|
202
|
+
if not is_none_arm:
|
|
203
|
+
self.ctx.func.narrowed_types[subject_name] = effective_type.inner
|
|
204
|
+
|
|
205
|
+
# Narrow Literal subject to matched value(s)
|
|
206
|
+
if is_literal and subject_name is not None:
|
|
207
|
+
matched = self._extract_literal_pattern_values(case.pattern, effective_type)
|
|
208
|
+
if matched is not None:
|
|
209
|
+
narrowed = LiteralType(effective_type.base_type, tuple(matched))
|
|
210
|
+
self.ctx.func.narrowed_types[subject_name] = narrowed
|
|
211
|
+
facts = {subject_name: narrowed}
|
|
212
|
+
case.type_facts = self.stmts._filter_union_codegen_facts(facts)
|
|
213
|
+
|
|
214
|
+
# Analyze guard expression (pattern bindings are in scope)
|
|
215
|
+
if case.guard is not None:
|
|
216
|
+
self.expr.analyze_expr(case.guard)
|
|
217
|
+
|
|
218
|
+
for s in case.body:
|
|
219
|
+
self.stmts.analyze_stmt(s)
|
|
220
|
+
|
|
221
|
+
arm_states.append(self.stmts.init.save())
|
|
222
|
+
arm_consumed.append((self.ctx.func.current_consumed_own_params.copy(), self.ctx.func.init_terminated))
|
|
223
|
+
arm_bindings.append(dict(self.ctx.func.current_scope.bindings))
|
|
224
|
+
|
|
225
|
+
pat = case.pattern
|
|
226
|
+
if isinstance(pat, TpyAsPattern):
|
|
227
|
+
pat = pat.pattern
|
|
228
|
+
if isinstance(pat, (TpyWildcardPattern, TpyCapturePattern)) and case.guard is None:
|
|
229
|
+
had_wildcard = True
|
|
230
|
+
# Class pattern on concrete record with no conditions is always-matching
|
|
231
|
+
elif ((is_record or is_optional) and isinstance(pat, TpyClassPattern)
|
|
232
|
+
and case.guard is None
|
|
233
|
+
and not any(self._is_constraining_sub_pattern(sub)
|
|
234
|
+
for _, sub in pat.keywords)):
|
|
235
|
+
had_wildcard = True
|
|
236
|
+
|
|
237
|
+
# Exhaustiveness check for finite-valued types
|
|
238
|
+
missing = (
|
|
239
|
+
[] if had_wildcard
|
|
240
|
+
else self._match_missing_cases(effective_type, seen_types, seen_values)
|
|
241
|
+
)
|
|
242
|
+
stmt.is_exhaustive = not missing
|
|
243
|
+
if missing:
|
|
244
|
+
if missing == [None]:
|
|
245
|
+
msg = (
|
|
246
|
+
f"non-exhaustive match on '{effective_type}'; "
|
|
247
|
+
f"no unconditional catch-all arm "
|
|
248
|
+
f"(add 'case _:' to suppress)"
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
msg = (
|
|
252
|
+
f"non-exhaustive match on '{effective_type}'; "
|
|
253
|
+
f"missing: {', '.join(missing)} "
|
|
254
|
+
f"(add 'case _:' to suppress)"
|
|
255
|
+
)
|
|
256
|
+
self.ctx.warning(msg, stmt)
|
|
257
|
+
|
|
258
|
+
# Merge flow states across all arms
|
|
259
|
+
self._merge_match_arms(arm_states, before)
|
|
260
|
+
|
|
261
|
+
# Merge consumed Own[T] params: intersect non-terminated arms.
|
|
262
|
+
# Terminated arms don't affect live continuation (same as if/else).
|
|
263
|
+
if arm_consumed:
|
|
264
|
+
live_sets = [s for s, terminated in arm_consumed if not terminated]
|
|
265
|
+
dead_sets = [s for s, terminated in arm_consumed if terminated]
|
|
266
|
+
if live_sets:
|
|
267
|
+
merged_consumed = live_sets[0]
|
|
268
|
+
for s in live_sets[1:]:
|
|
269
|
+
merged_consumed = merged_consumed & s
|
|
270
|
+
self.ctx.func.current_consumed_own_params = merged_consumed
|
|
271
|
+
elif dead_sets:
|
|
272
|
+
self.ctx.func.current_consumed_own_params = set().union(*dead_sets)
|
|
273
|
+
else:
|
|
274
|
+
self.ctx.func.current_consumed_own_params = consumed_before
|
|
275
|
+
|
|
276
|
+
# Restore scope bindings, merging types from arms
|
|
277
|
+
self.ctx.func.current_scope.bindings = dict(bindings_before)
|
|
278
|
+
self.stmts._restore_ns_var_types(ns_types_before)
|
|
279
|
+
for arm_b in arm_bindings:
|
|
280
|
+
for name, ty in arm_b.items():
|
|
281
|
+
if name not in bindings_before:
|
|
282
|
+
self.ctx.func.current_scope.define(name, ty)
|
|
283
|
+
|
|
284
|
+
self.stmts._sync_promoted_var_types(
|
|
285
|
+
set().union(*(set(b) for b in arm_bindings))
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
# Pre-declare variables first declared inside match arms
|
|
289
|
+
if not self.ctx.func.init_terminated:
|
|
290
|
+
branch_new = set(self.ctx.func.current_scope.bindings.keys()) - scope_before
|
|
291
|
+
newly_assigned = self.ctx.func.definitely_assigned - assigned_before
|
|
292
|
+
predecl = (branch_new & newly_assigned) - self.ctx.func.global_declarations
|
|
293
|
+
else:
|
|
294
|
+
predecl = set()
|
|
295
|
+
if predecl:
|
|
296
|
+
self.ctx.if_branch_decls[id(stmt)] = {
|
|
297
|
+
name: self.ctx.func.current_scope.lookup(name)
|
|
298
|
+
for name in sorted(predecl)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
def _match_case_narrowing_facts(
|
|
302
|
+
self, pattern: TpyPattern, subject_name: str | None,
|
|
303
|
+
subject_type: TpyType,
|
|
304
|
+
) -> dict[str, TpyType]:
|
|
305
|
+
"""Compute narrowing facts for a match case pattern."""
|
|
306
|
+
if subject_name is None:
|
|
307
|
+
return {}
|
|
308
|
+
# Unwrap as-pattern to get inner
|
|
309
|
+
inner = pattern
|
|
310
|
+
if isinstance(inner, TpyAsPattern):
|
|
311
|
+
inner = inner.pattern
|
|
312
|
+
if isinstance(inner, TpyClassPattern) and inner.resolved_type is not None:
|
|
313
|
+
return {subject_name: inner.resolved_type}
|
|
314
|
+
return {}
|
|
315
|
+
|
|
316
|
+
def _merge_match_arms(
|
|
317
|
+
self, arm_states: list['FlowFacts'], before: 'FlowFacts',
|
|
318
|
+
) -> None:
|
|
319
|
+
"""Merge flow states from multiple match arms.
|
|
320
|
+
|
|
321
|
+
Uses the same logic as merge_branches: intersect definitely_assigned
|
|
322
|
+
across non-terminated arms, union across terminated arms.
|
|
323
|
+
"""
|
|
324
|
+
if not arm_states:
|
|
325
|
+
self.stmts.init.restore(before)
|
|
326
|
+
return
|
|
327
|
+
if len(arm_states) == 1:
|
|
328
|
+
self.stmts.init.restore(arm_states[0])
|
|
329
|
+
return
|
|
330
|
+
# Pairwise merge: merge first two, then merge result with next, etc.
|
|
331
|
+
self.stmts.init.restore(arm_states[0])
|
|
332
|
+
for i in range(1, len(arm_states)):
|
|
333
|
+
current = self.stmts.init.save()
|
|
334
|
+
self.stmts.init.restore(before)
|
|
335
|
+
self.stmts.init.merge_branches(current, arm_states[i])
|
|
336
|
+
|
|
337
|
+
def _match_missing_cases(
|
|
338
|
+
self, subject_type: TpyType,
|
|
339
|
+
seen_types: set[str], seen_values: set[object],
|
|
340
|
+
) -> list[str]:
|
|
341
|
+
"""Return human-readable names of uncovered cases for finite-valued types."""
|
|
342
|
+
if isinstance(subject_type, UnionType):
|
|
343
|
+
return [
|
|
344
|
+
"None" if isinstance(m, NoneType) else str(m)
|
|
345
|
+
for m in subject_type.members
|
|
346
|
+
if str(m) not in seen_types
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
if is_enum_type(subject_type):
|
|
350
|
+
einfo = enum_info_of(subject_type)
|
|
351
|
+
return [
|
|
352
|
+
f"{subject_type.name}.{name}"
|
|
353
|
+
for name in einfo.members
|
|
354
|
+
if (subject_type.name, name) not in seen_values
|
|
355
|
+
]
|
|
356
|
+
|
|
357
|
+
if isinstance(subject_type, OptionalType):
|
|
358
|
+
if None not in seen_values:
|
|
359
|
+
return ["None"]
|
|
360
|
+
return []
|
|
361
|
+
|
|
362
|
+
if is_bool_type(subject_type):
|
|
363
|
+
missing: list[str] = []
|
|
364
|
+
if True not in seen_values:
|
|
365
|
+
missing.append("True")
|
|
366
|
+
if False not in seen_values:
|
|
367
|
+
missing.append("False")
|
|
368
|
+
return missing
|
|
369
|
+
|
|
370
|
+
if isinstance(subject_type, LiteralType):
|
|
371
|
+
all_values = {v.value for v in subject_type.values}
|
|
372
|
+
missing = sorted(
|
|
373
|
+
(str(v) if not isinstance(v, str) else f'"{v}"')
|
|
374
|
+
for v in all_values if v not in seen_values
|
|
375
|
+
)
|
|
376
|
+
return missing
|
|
377
|
+
|
|
378
|
+
if isinstance(subject_type, NominalType) and subject_type.is_user_record:
|
|
379
|
+
return [None] # type: ignore[list-item] # sentinel: no enumerable missing cases
|
|
380
|
+
|
|
381
|
+
return []
|
|
382
|
+
|
|
383
|
+
def _analyze_pattern(
|
|
384
|
+
self, pattern: TpyPattern, subject_type: UnionType,
|
|
385
|
+
seen_types: set[str], bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
386
|
+
) -> None:
|
|
387
|
+
"""Analyze a pattern against the subject type and collect bindings."""
|
|
388
|
+
if isinstance(pattern, TpyWildcardPattern):
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
elif isinstance(pattern, TpyCapturePattern):
|
|
392
|
+
bindings[pattern.name] = subject_type
|
|
393
|
+
|
|
394
|
+
elif isinstance(pattern, TpyAsPattern):
|
|
395
|
+
# `case None as x:` has no value to bind (NoneType is monostate);
|
|
396
|
+
# reject rather than silently dropping the binding.
|
|
397
|
+
if (isinstance(pattern.pattern, TpyLiteralPattern)
|
|
398
|
+
and pattern.pattern.value is None):
|
|
399
|
+
raise self.ctx.error(
|
|
400
|
+
"'as' binding not allowed on 'case None:'", pattern,
|
|
401
|
+
)
|
|
402
|
+
self._analyze_pattern(pattern.pattern, subject_type, seen_types, bindings, stmt)
|
|
403
|
+
# Bind as-variable to narrowed type when inner pattern is a class
|
|
404
|
+
if isinstance(pattern.pattern, TpyClassPattern) and pattern.pattern.resolved_type is not None:
|
|
405
|
+
bindings[pattern.name] = pattern.pattern.resolved_type
|
|
406
|
+
else:
|
|
407
|
+
bindings[pattern.name] = subject_type
|
|
408
|
+
|
|
409
|
+
elif isinstance(pattern, TpyClassPattern):
|
|
410
|
+
self._analyze_class_pattern(pattern, subject_type, seen_types, bindings, stmt)
|
|
411
|
+
|
|
412
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
413
|
+
self._analyze_or_pattern(pattern, subject_type, seen_types, bindings, stmt, kind=OrPatternKind.UNION)
|
|
414
|
+
|
|
415
|
+
elif isinstance(pattern, TpyLiteralPattern) and pattern.value is None:
|
|
416
|
+
if not subject_type.has_none_member():
|
|
417
|
+
raise self.ctx.error(
|
|
418
|
+
f"'case None:' requires None to be a member of the "
|
|
419
|
+
f"union subject; got '{subject_type}'", pattern,
|
|
420
|
+
)
|
|
421
|
+
none_key = str(NoneType())
|
|
422
|
+
if none_key in seen_types:
|
|
423
|
+
raise self.ctx.error(
|
|
424
|
+
"duplicate 'case None:' arm", pattern,
|
|
425
|
+
)
|
|
426
|
+
seen_types.add(none_key)
|
|
427
|
+
pattern.resolved_type = NoneType()
|
|
428
|
+
|
|
429
|
+
else:
|
|
430
|
+
raise self.ctx.error(
|
|
431
|
+
f"Unsupported pattern type in match on union: "
|
|
432
|
+
f"{type(pattern).__name__}", pattern
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def _analyze_class_pattern(
|
|
436
|
+
self, pattern: TpyClassPattern, subject_type: UnionType,
|
|
437
|
+
seen_types: set[str], bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
438
|
+
) -> None:
|
|
439
|
+
"""Analyze a class pattern: validate union membership and field bindings."""
|
|
440
|
+
if not isinstance(pattern.cls, TpyName):
|
|
441
|
+
raise self.ctx.error(
|
|
442
|
+
"class pattern must use a simple name", pattern
|
|
443
|
+
)
|
|
444
|
+
cls_name = pattern.cls.name
|
|
445
|
+
|
|
446
|
+
# Resolve to a type and find matching union member.
|
|
447
|
+
# Try exact match first (records, primitives), then fall back to
|
|
448
|
+
# searching union members by base name (handles parameterized types
|
|
449
|
+
# like list[Tree], Box[str] matched by bare list(), Box()).
|
|
450
|
+
record = self.ctx.registry.get_record(cls_name)
|
|
451
|
+
resolved_type = self._resolve_pattern_type(
|
|
452
|
+
cls_name, subject_type, record is not None, pattern)
|
|
453
|
+
|
|
454
|
+
pattern.resolved_type = resolved_type
|
|
455
|
+
|
|
456
|
+
if record is not None:
|
|
457
|
+
self._resolve_class_pattern_fields(pattern, record, bindings)
|
|
458
|
+
elif pattern.keywords:
|
|
459
|
+
raise self.ctx.error(
|
|
460
|
+
f"type '{cls_name}' does not support field patterns", pattern
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
# Build type key for duplicate detection.
|
|
464
|
+
# Include union field guards so that e.g. Box(value=Cat()) and
|
|
465
|
+
# Box(value=Dog()) on Box[Cat|Dog] are distinct cases.
|
|
466
|
+
type_key = self._build_pattern_type_key(pattern, resolved_type)
|
|
467
|
+
if type_key in seen_types:
|
|
468
|
+
raise self.ctx.error(
|
|
469
|
+
f"duplicate case for '{cls_name}' in match statement", pattern
|
|
470
|
+
)
|
|
471
|
+
seen_types.add(type_key)
|
|
472
|
+
|
|
473
|
+
def _resolve_pattern_type(
|
|
474
|
+
self, name: str, subject_type: UnionType,
|
|
475
|
+
is_record: bool, pattern: TpyClassPattern,
|
|
476
|
+
) -> TpyType:
|
|
477
|
+
"""Resolve a type name in a match class pattern against a union subject.
|
|
478
|
+
|
|
479
|
+
Resolution order:
|
|
480
|
+
1. Exact match: record NominalType or primitive (Int32, str, bool, ...)
|
|
481
|
+
2. Name-based member search: find the union member whose base name
|
|
482
|
+
matches (handles parameterized types like list[T], Box[str])
|
|
483
|
+
"""
|
|
484
|
+
resolved = _resolve_concrete_type_name(name)
|
|
485
|
+
if resolved is None and is_record:
|
|
486
|
+
# Mint qname so exact-match against union members (line 441
|
|
487
|
+
# below) works under strict NominalType equality. Without
|
|
488
|
+
# the qname, the match fails for cross-module records and
|
|
489
|
+
# we fall through to the short-name fuzzy path.
|
|
490
|
+
info = self.ctx.registry.get_record(name)
|
|
491
|
+
qname = info.qualified_name() if info else None
|
|
492
|
+
resolved = NominalType(name, _module_qname=qname)
|
|
493
|
+
# Check exact match against union members
|
|
494
|
+
if resolved is not None:
|
|
495
|
+
if any(m == resolved for m in subject_type.members):
|
|
496
|
+
return resolved
|
|
497
|
+
# Known type but not a union member -- give specific error,
|
|
498
|
+
# unless it could match as a parameterized type (fall through)
|
|
499
|
+
if not any(getattr(m, 'name', None) == name for m in subject_type.members):
|
|
500
|
+
raise self.ctx.error(
|
|
501
|
+
f"'{name}' is not a member of union '{subject_type}'", pattern
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
# Fall back: search union members by base name (for parameterized types)
|
|
505
|
+
matches = [m for m in subject_type.members
|
|
506
|
+
if getattr(m, 'name', None) == name]
|
|
507
|
+
if len(matches) == 1:
|
|
508
|
+
return matches[0]
|
|
509
|
+
if len(matches) > 1:
|
|
510
|
+
# Try disambiguation using field type sub-patterns
|
|
511
|
+
# (e.g. Box(value=str()) narrows Box[str] | Box[int] to Box[str])
|
|
512
|
+
disambiguated = self._disambiguate_by_field_types(
|
|
513
|
+
name, matches, pattern)
|
|
514
|
+
if disambiguated is not None:
|
|
515
|
+
return disambiguated
|
|
516
|
+
raise self.ctx.error(
|
|
517
|
+
f"ambiguous '{name}' pattern: union has multiple {name} members",
|
|
518
|
+
pattern,
|
|
519
|
+
)
|
|
520
|
+
raise self.ctx.error(f"unknown type '{name}' in match pattern", pattern)
|
|
521
|
+
|
|
522
|
+
def _analyze_class_pattern_record(
|
|
523
|
+
self, pattern: TpyClassPattern, subject_type: NominalType,
|
|
524
|
+
bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
525
|
+
) -> None:
|
|
526
|
+
"""Analyze a class pattern on a concrete record subject (field-value matching)."""
|
|
527
|
+
if not isinstance(pattern.cls, TpyName):
|
|
528
|
+
raise self.ctx.error(
|
|
529
|
+
"class pattern must use a simple name", pattern
|
|
530
|
+
)
|
|
531
|
+
cls_name = pattern.cls.name
|
|
532
|
+
|
|
533
|
+
record = self.ctx.registry.get_record(cls_name)
|
|
534
|
+
if record is None:
|
|
535
|
+
raise self.ctx.error(f"unknown type '{cls_name}' in match pattern", pattern)
|
|
536
|
+
|
|
537
|
+
if cls_name != subject_type.name:
|
|
538
|
+
raise self.ctx.error(
|
|
539
|
+
f"class pattern '{cls_name}' does not match "
|
|
540
|
+
f"subject type '{subject_type.name}'", pattern
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
pattern.resolved_type = subject_type
|
|
544
|
+
self._resolve_class_pattern_fields(pattern, record, bindings)
|
|
545
|
+
|
|
546
|
+
def _resolve_class_pattern_fields(
|
|
547
|
+
self, pattern: TpyClassPattern, record: RecordInfo,
|
|
548
|
+
bindings: dict[str, TpyType],
|
|
549
|
+
) -> None:
|
|
550
|
+
"""Resolve positional patterns and validate keyword field bindings."""
|
|
551
|
+
cls_name = pattern.cls.name if isinstance(pattern.cls, TpyName) else "?"
|
|
552
|
+
|
|
553
|
+
# Use match_args for positional resolution (if set by macro), own fields otherwise.
|
|
554
|
+
# For field type lookup, include inherited fields.
|
|
555
|
+
all_fields = self.ctx.registry.get_all_fields(record)
|
|
556
|
+
match_args = record.match_args if record.match_args is not None else tuple(
|
|
557
|
+
f.name for f in record.fields
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
# Resolve positional patterns to keyword patterns via match_args order
|
|
561
|
+
if pattern.positional:
|
|
562
|
+
n = len(match_args)
|
|
563
|
+
if len(pattern.positional) > n:
|
|
564
|
+
noun = "positional pattern" if n == 1 else "positional patterns"
|
|
565
|
+
raise self.ctx.error(
|
|
566
|
+
f"'{cls_name}' accepts {n} {noun} "
|
|
567
|
+
f"but {len(pattern.positional)} were given", pattern
|
|
568
|
+
)
|
|
569
|
+
kwd_names = {name for name, _ in pattern.keywords}
|
|
570
|
+
resolved: list[tuple[str, TpyPattern]] = []
|
|
571
|
+
for i, sub_pat in enumerate(pattern.positional):
|
|
572
|
+
field_name = match_args[i]
|
|
573
|
+
if field_name in kwd_names:
|
|
574
|
+
raise self.ctx.error(
|
|
575
|
+
f"field '{field_name}' is bound both positionally and by keyword "
|
|
576
|
+
f"in pattern for '{cls_name}'", pattern
|
|
577
|
+
)
|
|
578
|
+
resolved.append((field_name, sub_pat))
|
|
579
|
+
resolved.extend(pattern.keywords)
|
|
580
|
+
pattern.keywords = resolved
|
|
581
|
+
pattern.positional = []
|
|
582
|
+
|
|
583
|
+
# Build type param substitution map for generic records
|
|
584
|
+
type_subst = self._build_type_subst(record, pattern.resolved_type)
|
|
585
|
+
|
|
586
|
+
# Validate keyword field bindings
|
|
587
|
+
for field_name, sub_pattern in pattern.keywords:
|
|
588
|
+
field_info = None
|
|
589
|
+
for f in all_fields:
|
|
590
|
+
if f.name == field_name:
|
|
591
|
+
field_info = f
|
|
592
|
+
break
|
|
593
|
+
if field_info is None:
|
|
594
|
+
raise self.ctx.error(
|
|
595
|
+
f"'{cls_name}' has no field '{field_name}'", pattern
|
|
596
|
+
)
|
|
597
|
+
# Resolve field type with type param substitution
|
|
598
|
+
field_type = _subst_type_params(field_info.type, type_subst) if type_subst else field_info.type
|
|
599
|
+
# Sub-pattern bindings
|
|
600
|
+
if isinstance(sub_pattern, TpyCapturePattern):
|
|
601
|
+
bindings[sub_pattern.name] = field_type
|
|
602
|
+
elif isinstance(sub_pattern, TpyWildcardPattern):
|
|
603
|
+
pass
|
|
604
|
+
elif isinstance(sub_pattern, TpyLiteralPattern):
|
|
605
|
+
pass # Literal comparison -- validated at codegen time
|
|
606
|
+
elif isinstance(sub_pattern, TpyClassPattern):
|
|
607
|
+
# Type sub-pattern (e.g., value=str()) -- type guard
|
|
608
|
+
matched_type = self._validate_field_type_pattern(sub_pattern, field_type, pattern)
|
|
609
|
+
# Recursively validate nested field patterns
|
|
610
|
+
if sub_pattern.keywords or sub_pattern.positional:
|
|
611
|
+
self._resolve_nested_class_fields(sub_pattern, matched_type, bindings)
|
|
612
|
+
elif isinstance(sub_pattern, TpyAsPattern):
|
|
613
|
+
inner_sub = sub_pattern.pattern
|
|
614
|
+
if isinstance(inner_sub, TpyClassPattern):
|
|
615
|
+
# Type sub-pattern with capture (e.g., value=str() as v)
|
|
616
|
+
matched_type = self._validate_field_type_pattern(inner_sub, field_type, pattern)
|
|
617
|
+
bindings[sub_pattern.name] = matched_type
|
|
618
|
+
# Recursively validate nested field patterns
|
|
619
|
+
if inner_sub.keywords or inner_sub.positional:
|
|
620
|
+
self._resolve_nested_class_fields(inner_sub, matched_type, bindings)
|
|
621
|
+
elif isinstance(inner_sub, TpyLiteralPattern):
|
|
622
|
+
bindings[sub_pattern.name] = field_type
|
|
623
|
+
elif isinstance(inner_sub, (TpyWildcardPattern, TpyCapturePattern)):
|
|
624
|
+
bindings[sub_pattern.name] = field_type
|
|
625
|
+
if isinstance(inner_sub, TpyCapturePattern):
|
|
626
|
+
bindings[inner_sub.name] = field_type
|
|
627
|
+
else:
|
|
628
|
+
raise self.ctx.error(
|
|
629
|
+
f"Unsupported sub-pattern in field binding: "
|
|
630
|
+
f"{type(inner_sub).__name__}", sub_pattern
|
|
631
|
+
)
|
|
632
|
+
else:
|
|
633
|
+
raise self.ctx.error(
|
|
634
|
+
f"Unsupported sub-pattern in field binding: "
|
|
635
|
+
f"{type(sub_pattern).__name__}", sub_pattern
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
def _analyze_pattern_nonunion(
|
|
639
|
+
self, pattern: TpyPattern, subject_type: TpyType,
|
|
640
|
+
seen_values: set[object], bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
641
|
+
) -> None:
|
|
642
|
+
"""Analyze a pattern for non-union subjects (enum, primitive, literal, wildcard, capture, as)."""
|
|
643
|
+
if isinstance(pattern, TpyWildcardPattern):
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
elif isinstance(pattern, TpyCapturePattern):
|
|
647
|
+
bindings[pattern.name] = subject_type
|
|
648
|
+
|
|
649
|
+
elif isinstance(pattern, TpyAsPattern):
|
|
650
|
+
self._analyze_pattern_nonunion(
|
|
651
|
+
pattern.pattern, subject_type, seen_values, bindings, stmt,
|
|
652
|
+
)
|
|
653
|
+
bindings[pattern.name] = subject_type
|
|
654
|
+
|
|
655
|
+
elif isinstance(pattern, TpyLiteralPattern):
|
|
656
|
+
self._validate_literal_pattern(pattern, subject_type)
|
|
657
|
+
self._check_duplicate_literal(pattern, seen_values)
|
|
658
|
+
|
|
659
|
+
elif isinstance(pattern, TpyValuePattern):
|
|
660
|
+
self._validate_value_pattern(pattern, subject_type)
|
|
661
|
+
self._check_duplicate_value(pattern, seen_values)
|
|
662
|
+
|
|
663
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
664
|
+
self._analyze_or_pattern(pattern, subject_type, seen_values, bindings, stmt, kind=OrPatternKind.NONUNION)
|
|
665
|
+
|
|
666
|
+
else:
|
|
667
|
+
raise self.ctx.error(
|
|
668
|
+
f"Unsupported pattern for {subject_type} subject: "
|
|
669
|
+
f"{type(pattern).__name__}", pattern
|
|
670
|
+
)
|
|
671
|
+
|
|
672
|
+
def _analyze_pattern_record(
|
|
673
|
+
self, pattern: TpyPattern, subject_type: NominalType,
|
|
674
|
+
bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
675
|
+
) -> None:
|
|
676
|
+
"""Analyze a pattern for concrete record subjects (field-value matching)."""
|
|
677
|
+
if isinstance(pattern, TpyWildcardPattern):
|
|
678
|
+
return
|
|
679
|
+
|
|
680
|
+
elif isinstance(pattern, TpyCapturePattern):
|
|
681
|
+
bindings[pattern.name] = subject_type
|
|
682
|
+
|
|
683
|
+
elif isinstance(pattern, TpyAsPattern):
|
|
684
|
+
self._analyze_pattern_record(
|
|
685
|
+
pattern.pattern, subject_type, bindings, stmt,
|
|
686
|
+
)
|
|
687
|
+
bindings[pattern.name] = subject_type
|
|
688
|
+
|
|
689
|
+
elif isinstance(pattern, TpyClassPattern):
|
|
690
|
+
self._analyze_class_pattern_record(pattern, subject_type, bindings, stmt)
|
|
691
|
+
|
|
692
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
693
|
+
self._analyze_or_pattern(
|
|
694
|
+
pattern, subject_type, set(), bindings, stmt, kind=OrPatternKind.RECORD,
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
else:
|
|
698
|
+
raise self.ctx.error(
|
|
699
|
+
f"Unsupported pattern for record subject '{subject_type.name}': "
|
|
700
|
+
f"{type(pattern).__name__}", pattern
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def _analyze_pattern_optional(
|
|
704
|
+
self, pattern: TpyPattern, subject_type: OptionalType,
|
|
705
|
+
seen_values: set[object], bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
706
|
+
) -> None:
|
|
707
|
+
"""Analyze a pattern for Optional subjects."""
|
|
708
|
+
if isinstance(pattern, TpyWildcardPattern):
|
|
709
|
+
return
|
|
710
|
+
|
|
711
|
+
elif isinstance(pattern, TpyCapturePattern):
|
|
712
|
+
# Capture matches the non-None value (case None: is a literal pattern)
|
|
713
|
+
bindings[pattern.name] = subject_type.inner
|
|
714
|
+
|
|
715
|
+
elif isinstance(pattern, TpyAsPattern):
|
|
716
|
+
self._analyze_pattern_optional(
|
|
717
|
+
pattern.pattern, subject_type, seen_values, bindings, stmt,
|
|
718
|
+
)
|
|
719
|
+
bindings[pattern.name] = subject_type.inner
|
|
720
|
+
|
|
721
|
+
elif isinstance(pattern, TpyLiteralPattern):
|
|
722
|
+
if pattern.value is None:
|
|
723
|
+
self._check_duplicate_literal(pattern, seen_values)
|
|
724
|
+
else:
|
|
725
|
+
# Literal match on the inner type (e.g. case 42: on Optional[Int32])
|
|
726
|
+
self._validate_literal_pattern(pattern, subject_type.inner)
|
|
727
|
+
self._check_duplicate_literal(pattern, seen_values)
|
|
728
|
+
|
|
729
|
+
elif isinstance(pattern, TpyValuePattern):
|
|
730
|
+
# Value pattern on inner type (e.g. case Color.RED: on Optional[Color])
|
|
731
|
+
self._validate_value_pattern(pattern, subject_type.inner)
|
|
732
|
+
self._check_duplicate_value(pattern, seen_values)
|
|
733
|
+
|
|
734
|
+
elif isinstance(pattern, TpyClassPattern):
|
|
735
|
+
# Class pattern on the inner type (e.g. case Point(): on Optional[Point])
|
|
736
|
+
inner = subject_type.inner
|
|
737
|
+
if isinstance(inner, NominalType) and inner.is_user_record:
|
|
738
|
+
record = self.ctx.registry.get_record(inner.name)
|
|
739
|
+
if record is not None:
|
|
740
|
+
self._analyze_class_pattern_record(pattern, inner, bindings, stmt)
|
|
741
|
+
return
|
|
742
|
+
raise self.ctx.error(
|
|
743
|
+
f"class pattern not valid for Optional inner type '{inner}'",
|
|
744
|
+
pattern,
|
|
745
|
+
)
|
|
746
|
+
|
|
747
|
+
elif isinstance(pattern, TpyOrPattern):
|
|
748
|
+
self._analyze_or_pattern(
|
|
749
|
+
pattern, subject_type, seen_values, bindings, stmt, kind=OrPatternKind.OPTIONAL,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
else:
|
|
753
|
+
raise self.ctx.error(
|
|
754
|
+
f"Unsupported pattern for Optional subject: "
|
|
755
|
+
f"{type(pattern).__name__}", pattern
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
def _analyze_or_pattern(
|
|
759
|
+
self, pattern: TpyOrPattern, subject_type: TpyType,
|
|
760
|
+
seen: set, bindings: dict[str, TpyType], stmt: TpyMatch,
|
|
761
|
+
kind: OrPatternKind,
|
|
762
|
+
) -> None:
|
|
763
|
+
"""Analyze an or-pattern: all alternatives must bind same variables with compatible types."""
|
|
764
|
+
if len(pattern.patterns) < 2:
|
|
765
|
+
raise self.ctx.error("or-pattern must have at least 2 alternatives", pattern)
|
|
766
|
+
|
|
767
|
+
first_bindings: dict[str, TpyType] | None = None
|
|
768
|
+
for alt in pattern.patterns:
|
|
769
|
+
alt_bindings: dict[str, TpyType] = {}
|
|
770
|
+
if kind is OrPatternKind.UNION:
|
|
771
|
+
self._analyze_pattern(alt, subject_type, seen, alt_bindings, stmt)
|
|
772
|
+
elif kind is OrPatternKind.RECORD:
|
|
773
|
+
self._analyze_pattern_record(alt, subject_type, alt_bindings, stmt)
|
|
774
|
+
elif kind is OrPatternKind.OPTIONAL:
|
|
775
|
+
self._analyze_pattern_optional(alt, subject_type, seen, alt_bindings, stmt)
|
|
776
|
+
else:
|
|
777
|
+
self._analyze_pattern_nonunion(alt, subject_type, seen, alt_bindings, stmt)
|
|
778
|
+
|
|
779
|
+
if first_bindings is None:
|
|
780
|
+
first_bindings = alt_bindings
|
|
781
|
+
else:
|
|
782
|
+
# Check same variable names
|
|
783
|
+
if set(alt_bindings.keys()) != set(first_bindings.keys()):
|
|
784
|
+
missing = set(first_bindings.keys()) - set(alt_bindings.keys())
|
|
785
|
+
extra = set(alt_bindings.keys()) - set(first_bindings.keys())
|
|
786
|
+
if missing:
|
|
787
|
+
raise self.ctx.error(
|
|
788
|
+
f"variable(s) {', '.join(sorted(missing))} not bound "
|
|
789
|
+
f"in all alternatives of or-pattern", alt
|
|
790
|
+
)
|
|
791
|
+
if extra:
|
|
792
|
+
raise self.ctx.error(
|
|
793
|
+
f"variable(s) {', '.join(sorted(extra))} not bound "
|
|
794
|
+
f"in all alternatives of or-pattern", alt
|
|
795
|
+
)
|
|
796
|
+
# Check compatible types
|
|
797
|
+
for name, ty in alt_bindings.items():
|
|
798
|
+
first_ty = first_bindings[name]
|
|
799
|
+
if ty != first_ty:
|
|
800
|
+
raise self.ctx.error(
|
|
801
|
+
f"variable '{name}' has type '{first_ty}' in first alternative "
|
|
802
|
+
f"but '{ty}' in another", alt
|
|
803
|
+
)
|
|
804
|
+
|
|
805
|
+
if first_bindings:
|
|
806
|
+
bindings.update(first_bindings)
|
|
807
|
+
|
|
808
|
+
def _validate_literal_pattern(
|
|
809
|
+
self, pattern: TpyLiteralPattern, subject_type: TpyType,
|
|
810
|
+
) -> None:
|
|
811
|
+
"""Validate that a literal pattern is compatible with the subject type."""
|
|
812
|
+
# Unwrap LiteralType to base_type for validation
|
|
813
|
+
check_type = subject_type.base_type if isinstance(subject_type, LiteralType) else subject_type
|
|
814
|
+
val = pattern.value
|
|
815
|
+
if val is None:
|
|
816
|
+
raise self.ctx.error(
|
|
817
|
+
"None literal pattern requires an Optional subject", pattern
|
|
818
|
+
)
|
|
819
|
+
if isinstance(val, bool):
|
|
820
|
+
if not is_bool_type(check_type):
|
|
821
|
+
raise self.ctx.error(
|
|
822
|
+
f"bool literal pattern not valid for subject type '{subject_type}'",
|
|
823
|
+
pattern,
|
|
824
|
+
)
|
|
825
|
+
elif isinstance(val, int):
|
|
826
|
+
if not (is_fixed_int_type(check_type) or is_big_int_type(check_type)
|
|
827
|
+
or is_enum_type(check_type)):
|
|
828
|
+
raise self.ctx.error(
|
|
829
|
+
f"int literal pattern not valid for subject type '{subject_type}'",
|
|
830
|
+
pattern,
|
|
831
|
+
)
|
|
832
|
+
elif isinstance(val, float):
|
|
833
|
+
if not is_float_type(check_type):
|
|
834
|
+
raise self.ctx.error(
|
|
835
|
+
f"float literal pattern not valid for subject type '{subject_type}'",
|
|
836
|
+
pattern,
|
|
837
|
+
)
|
|
838
|
+
elif isinstance(val, str):
|
|
839
|
+
if not is_any_str_type(check_type):
|
|
840
|
+
raise self.ctx.error(
|
|
841
|
+
f"str literal pattern not valid for subject type '{subject_type}'",
|
|
842
|
+
pattern,
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
def _extract_literal_pattern_values(
|
|
846
|
+
self, pattern: TpyPattern, lit_type: LiteralType,
|
|
847
|
+
) -> list[LiteralValue] | None:
|
|
848
|
+
"""Extract LiteralValues matched by a pattern. None for wildcard/capture."""
|
|
849
|
+
if isinstance(pattern, TpyAsPattern):
|
|
850
|
+
return self._extract_literal_pattern_values(pattern.pattern, lit_type)
|
|
851
|
+
if isinstance(pattern, (TpyWildcardPattern, TpyCapturePattern)):
|
|
852
|
+
return None
|
|
853
|
+
if isinstance(pattern, TpyLiteralPattern):
|
|
854
|
+
val = pattern.value
|
|
855
|
+
tag = (LiteralTag.BOOL if isinstance(val, bool)
|
|
856
|
+
else LiteralTag.INT if isinstance(val, int)
|
|
857
|
+
else LiteralTag.STR)
|
|
858
|
+
return [LiteralValue(tag, val)]
|
|
859
|
+
if isinstance(pattern, TpyOrPattern):
|
|
860
|
+
result: list[LiteralValue] = []
|
|
861
|
+
for alt in pattern.patterns:
|
|
862
|
+
sub = self._extract_literal_pattern_values(alt, lit_type)
|
|
863
|
+
if sub is None:
|
|
864
|
+
return None
|
|
865
|
+
result.extend(sub)
|
|
866
|
+
return result
|
|
867
|
+
return None
|
|
868
|
+
|
|
869
|
+
def _validate_value_pattern(
|
|
870
|
+
self, pattern: TpyValuePattern, subject_type: TpyType,
|
|
871
|
+
) -> None:
|
|
872
|
+
"""Validate a value pattern (e.g., Color.RED) against the subject type."""
|
|
873
|
+
val_type = self.expr.analyze_expr(pattern.expr)
|
|
874
|
+
if is_enum_type(subject_type):
|
|
875
|
+
if not is_enum_type(val_type) or val_type.name != subject_type.name:
|
|
876
|
+
raise self.ctx.error(
|
|
877
|
+
f"value pattern type '{val_type}' does not match "
|
|
878
|
+
f"subject type '{subject_type}'", pattern
|
|
879
|
+
)
|
|
880
|
+
else:
|
|
881
|
+
raise self.ctx.error(
|
|
882
|
+
f"value pattern (dotted name) not valid for subject type "
|
|
883
|
+
f"'{subject_type}'", pattern
|
|
884
|
+
)
|
|
885
|
+
|
|
886
|
+
def _check_duplicate_literal(
|
|
887
|
+
self, pattern: TpyLiteralPattern, seen: set[object],
|
|
888
|
+
) -> None:
|
|
889
|
+
"""Check for duplicate literal pattern values."""
|
|
890
|
+
key = pattern.value
|
|
891
|
+
if key in seen:
|
|
892
|
+
label = repr(key)
|
|
893
|
+
raise self.ctx.error(
|
|
894
|
+
f"duplicate case for {label} in match statement", pattern
|
|
895
|
+
)
|
|
896
|
+
seen.add(key)
|
|
897
|
+
|
|
898
|
+
def _check_duplicate_value(
|
|
899
|
+
self, pattern: TpyValuePattern, seen: set[object],
|
|
900
|
+
) -> None:
|
|
901
|
+
"""Check for duplicate value pattern (e.g. Color.RED)."""
|
|
902
|
+
if isinstance(pattern.expr, TpyFieldAccess):
|
|
903
|
+
obj_name = pattern.expr.obj.name if isinstance(pattern.expr.obj, TpyName) else "?"
|
|
904
|
+
key = (obj_name, pattern.expr.field)
|
|
905
|
+
if key in seen:
|
|
906
|
+
raise self.ctx.error(
|
|
907
|
+
f"duplicate case for '{obj_name}.{pattern.expr.field}' "
|
|
908
|
+
f"in match statement", pattern
|
|
909
|
+
)
|
|
910
|
+
seen.add(key)
|
|
911
|
+
|
|
912
|
+
# ------------------------------------------------------------------
|
|
913
|
+
# Nested type sub-pattern helpers
|
|
914
|
+
# ------------------------------------------------------------------
|
|
915
|
+
|
|
916
|
+
@staticmethod
|
|
917
|
+
def _build_pattern_type_key(
|
|
918
|
+
pattern: TpyClassPattern, resolved_type: TpyType,
|
|
919
|
+
) -> str:
|
|
920
|
+
"""Build a key for duplicate-case detection that includes union field guards."""
|
|
921
|
+
key = str(resolved_type)
|
|
922
|
+
for _, sub in pattern.keywords:
|
|
923
|
+
inner = sub
|
|
924
|
+
if isinstance(inner, TpyAsPattern):
|
|
925
|
+
inner = inner.pattern
|
|
926
|
+
if isinstance(inner, TpyClassPattern) and inner.is_union_field_guard:
|
|
927
|
+
key += f"+{inner.resolved_type}"
|
|
928
|
+
return key
|
|
929
|
+
|
|
930
|
+
@staticmethod
|
|
931
|
+
def _is_constraining_sub_pattern(sub: TpyPattern) -> bool:
|
|
932
|
+
"""Check if a field sub-pattern adds a constraint (not always-matching)."""
|
|
933
|
+
if isinstance(sub, (TpyLiteralPattern, TpyValuePattern, TpyClassPattern)):
|
|
934
|
+
return True
|
|
935
|
+
if isinstance(sub, TpyAsPattern) and isinstance(sub.pattern, TpyClassPattern):
|
|
936
|
+
return True
|
|
937
|
+
return False
|
|
938
|
+
|
|
939
|
+
@staticmethod
|
|
940
|
+
def _build_type_subst(
|
|
941
|
+
record: 'RecordInfo', resolved_type: TpyType | None,
|
|
942
|
+
) -> dict[str, TpyType]:
|
|
943
|
+
"""Build type param substitution map from resolved_type's type_args."""
|
|
944
|
+
if (resolved_type is None or not isinstance(resolved_type, NominalType)
|
|
945
|
+
or not record.type_params or not resolved_type.type_args):
|
|
946
|
+
return {}
|
|
947
|
+
subst: dict[str, TpyType] = {}
|
|
948
|
+
for param_name, arg in zip(record.type_params, resolved_type.type_args):
|
|
949
|
+
if isinstance(arg, TpyType):
|
|
950
|
+
subst[param_name] = arg
|
|
951
|
+
return subst
|
|
952
|
+
|
|
953
|
+
def _disambiguate_by_field_types(
|
|
954
|
+
self, cls_name: str, candidates: list[TpyType],
|
|
955
|
+
pattern: TpyClassPattern,
|
|
956
|
+
) -> TpyType | None:
|
|
957
|
+
"""Try to disambiguate multiple same-name union members using field type sub-patterns.
|
|
958
|
+
|
|
959
|
+
For example, Box(value=str()) on union Box[str] | Box[int] narrows to Box[str]
|
|
960
|
+
by checking which candidate's 'value' field type matches 'str'.
|
|
961
|
+
|
|
962
|
+
Recurses into nested type sub-patterns, so Box(value=Box(value=str()))
|
|
963
|
+
can disambiguate Box[Box[str]] | Box[Box[int]].
|
|
964
|
+
|
|
965
|
+
Returns the unique matching candidate, or None if no type guards are present.
|
|
966
|
+
Raises an error if type guards are present but no candidate matches.
|
|
967
|
+
"""
|
|
968
|
+
record = self.ctx.registry.get_record(cls_name)
|
|
969
|
+
if record is None:
|
|
970
|
+
return None
|
|
971
|
+
if not self._has_type_sub_patterns(pattern, record):
|
|
972
|
+
return None
|
|
973
|
+
|
|
974
|
+
all_fields = self.ctx.registry.get_all_fields(record)
|
|
975
|
+
matching: list[TpyType] = []
|
|
976
|
+
for candidate in candidates:
|
|
977
|
+
if self._pattern_matches_candidate(record, candidate, all_fields, pattern):
|
|
978
|
+
matching.append(candidate)
|
|
979
|
+
|
|
980
|
+
if len(matching) == 1:
|
|
981
|
+
return matching[0]
|
|
982
|
+
if len(matching) == 0:
|
|
983
|
+
raise self.ctx.error(
|
|
984
|
+
f"no '{cls_name}' variant matches the field type pattern(s)",
|
|
985
|
+
pattern,
|
|
986
|
+
)
|
|
987
|
+
return None
|
|
988
|
+
|
|
989
|
+
def _has_type_sub_patterns(
|
|
990
|
+
self, pattern: TpyClassPattern, record: 'RecordInfo',
|
|
991
|
+
) -> bool:
|
|
992
|
+
"""Check if a class pattern has any type sub-patterns (TpyClassPattern in fields)."""
|
|
993
|
+
match_args = record.match_args if record.match_args is not None else tuple(
|
|
994
|
+
f.name for f in record.fields
|
|
995
|
+
)
|
|
996
|
+
for i, sub_pat in enumerate(pattern.positional):
|
|
997
|
+
if i >= len(match_args):
|
|
998
|
+
break
|
|
999
|
+
inner = sub_pat
|
|
1000
|
+
if isinstance(inner, TpyAsPattern):
|
|
1001
|
+
inner = inner.pattern
|
|
1002
|
+
if isinstance(inner, TpyClassPattern):
|
|
1003
|
+
return True
|
|
1004
|
+
for _, sub_pat in pattern.keywords:
|
|
1005
|
+
inner = sub_pat
|
|
1006
|
+
if isinstance(inner, TpyAsPattern):
|
|
1007
|
+
inner = inner.pattern
|
|
1008
|
+
if isinstance(inner, TpyClassPattern):
|
|
1009
|
+
return True
|
|
1010
|
+
return False
|
|
1011
|
+
|
|
1012
|
+
def _pattern_matches_candidate(
|
|
1013
|
+
self, record: 'RecordInfo', candidate: TpyType,
|
|
1014
|
+
all_fields: list, pattern: TpyClassPattern,
|
|
1015
|
+
) -> bool:
|
|
1016
|
+
"""Recursively check if all type sub-patterns in a class pattern match a candidate."""
|
|
1017
|
+
subst = self._build_type_subst(record, candidate)
|
|
1018
|
+
|
|
1019
|
+
# Build combined keyword list (positional resolved via match_args + explicit keywords)
|
|
1020
|
+
match_args = record.match_args if record.match_args is not None else tuple(
|
|
1021
|
+
f.name for f in record.fields
|
|
1022
|
+
)
|
|
1023
|
+
keywords: list[tuple[str, TpyPattern]] = [
|
|
1024
|
+
(match_args[i], sub_pat)
|
|
1025
|
+
for i, sub_pat in enumerate(pattern.positional)
|
|
1026
|
+
if i < len(match_args)
|
|
1027
|
+
]
|
|
1028
|
+
keywords.extend(pattern.keywords)
|
|
1029
|
+
|
|
1030
|
+
for field_name, sub_pat in keywords:
|
|
1031
|
+
inner = sub_pat
|
|
1032
|
+
if isinstance(inner, TpyAsPattern):
|
|
1033
|
+
inner = inner.pattern
|
|
1034
|
+
if not isinstance(inner, TpyClassPattern) or not isinstance(inner.cls, TpyName):
|
|
1035
|
+
continue
|
|
1036
|
+
|
|
1037
|
+
type_name = inner.cls.name
|
|
1038
|
+
field_type = next((f.type for f in all_fields if f.name == field_name), None)
|
|
1039
|
+
if field_type is None:
|
|
1040
|
+
return False
|
|
1041
|
+
resolved = _subst_type_params(field_type, subst) if subst else field_type
|
|
1042
|
+
|
|
1043
|
+
if not self._type_pattern_compatible(type_name, resolved, inner):
|
|
1044
|
+
return False
|
|
1045
|
+
return True
|
|
1046
|
+
|
|
1047
|
+
def _type_pattern_compatible(
|
|
1048
|
+
self, type_name: str, field_type: TpyType,
|
|
1049
|
+
inner_pattern: TpyClassPattern,
|
|
1050
|
+
) -> bool:
|
|
1051
|
+
"""Check if a type name matches a field type, recursing into inner patterns."""
|
|
1052
|
+
# Resolve to a concrete type
|
|
1053
|
+
pattern_type = _resolve_concrete_type_name(type_name)
|
|
1054
|
+
if pattern_type is not None:
|
|
1055
|
+
if field_type == pattern_type:
|
|
1056
|
+
return True
|
|
1057
|
+
if isinstance(field_type, UnionType):
|
|
1058
|
+
return any(m == pattern_type for m in field_type.members)
|
|
1059
|
+
return False
|
|
1060
|
+
|
|
1061
|
+
# User record: match by name
|
|
1062
|
+
inner_record = self.ctx.registry.get_record(type_name)
|
|
1063
|
+
if inner_record is None:
|
|
1064
|
+
return False
|
|
1065
|
+
|
|
1066
|
+
# Collect matching types from field_type
|
|
1067
|
+
if isinstance(field_type, NominalType) and field_type.name == type_name:
|
|
1068
|
+
candidates = [field_type]
|
|
1069
|
+
elif isinstance(field_type, UnionType):
|
|
1070
|
+
candidates = [m for m in field_type.members
|
|
1071
|
+
if isinstance(m, NominalType) and m.name == type_name]
|
|
1072
|
+
else:
|
|
1073
|
+
return False
|
|
1074
|
+
|
|
1075
|
+
if not candidates:
|
|
1076
|
+
return False
|
|
1077
|
+
|
|
1078
|
+
# If the inner pattern has further type sub-patterns, use them to narrow
|
|
1079
|
+
has_inner = (inner_pattern.keywords or inner_pattern.positional) and self._has_type_sub_patterns(inner_pattern, inner_record)
|
|
1080
|
+
if has_inner:
|
|
1081
|
+
inner_fields = self.ctx.registry.get_all_fields(inner_record)
|
|
1082
|
+
candidates = [c for c in candidates
|
|
1083
|
+
if self._pattern_matches_candidate(
|
|
1084
|
+
inner_record, c, inner_fields, inner_pattern)]
|
|
1085
|
+
|
|
1086
|
+
return len(candidates) >= 1
|
|
1087
|
+
|
|
1088
|
+
def _validate_field_type_pattern(
|
|
1089
|
+
self, sub_pattern: TpyClassPattern, field_type: TpyType,
|
|
1090
|
+
parent: TpyPattern,
|
|
1091
|
+
) -> TpyType:
|
|
1092
|
+
"""Validate a type sub-pattern against the resolved field type.
|
|
1093
|
+
|
|
1094
|
+
Returns the matched type (for binding). Sets sub_pattern.resolved_type
|
|
1095
|
+
when the field is a union (signals codegen to emit holds_alternative).
|
|
1096
|
+
"""
|
|
1097
|
+
if not isinstance(sub_pattern.cls, TpyName):
|
|
1098
|
+
raise self.ctx.error(
|
|
1099
|
+
"type pattern in field must use a simple name", sub_pattern
|
|
1100
|
+
)
|
|
1101
|
+
cls_name = sub_pattern.cls.name
|
|
1102
|
+
|
|
1103
|
+
pattern_type = _resolve_concrete_type_name(cls_name)
|
|
1104
|
+
if pattern_type is None:
|
|
1105
|
+
info = self.ctx.registry.get_record(cls_name)
|
|
1106
|
+
if info is not None:
|
|
1107
|
+
pattern_type = NominalType(cls_name, _module_qname=info.qualified_name())
|
|
1108
|
+
if pattern_type is None:
|
|
1109
|
+
raise self.ctx.error(
|
|
1110
|
+
f"unknown type '{cls_name}' in field type pattern", sub_pattern
|
|
1111
|
+
)
|
|
1112
|
+
|
|
1113
|
+
# Exact match: compile-time type guard (no runtime check)
|
|
1114
|
+
if field_type == pattern_type:
|
|
1115
|
+
return field_type
|
|
1116
|
+
if isinstance(field_type, NominalType) and field_type.name == cls_name:
|
|
1117
|
+
return field_type
|
|
1118
|
+
|
|
1119
|
+
# Union field: check if field_type is a union containing pattern_type
|
|
1120
|
+
is_record = isinstance(pattern_type, NominalType)
|
|
1121
|
+
if isinstance(field_type, UnionType):
|
|
1122
|
+
if is_record:
|
|
1123
|
+
# Records: match by name (handles parameterized types like Box[str])
|
|
1124
|
+
matches = [m for m in field_type.members
|
|
1125
|
+
if isinstance(m, NominalType) and m.name == cls_name]
|
|
1126
|
+
else:
|
|
1127
|
+
# Primitives: exact type match
|
|
1128
|
+
matches = [m for m in field_type.members if m == pattern_type]
|
|
1129
|
+
|
|
1130
|
+
if len(matches) == 1:
|
|
1131
|
+
sub_pattern.resolved_type = matches[0]
|
|
1132
|
+
sub_pattern.is_union_field_guard = True
|
|
1133
|
+
return matches[0]
|
|
1134
|
+
|
|
1135
|
+
if len(matches) > 1:
|
|
1136
|
+
# Multiple same-name members -- try inner patterns for disambiguation
|
|
1137
|
+
inner_record = self.ctx.registry.get_record(cls_name)
|
|
1138
|
+
if inner_record is not None and (sub_pattern.keywords or sub_pattern.positional):
|
|
1139
|
+
disambiguated = self._disambiguate_by_field_types(
|
|
1140
|
+
cls_name, matches, sub_pattern)
|
|
1141
|
+
if disambiguated is not None:
|
|
1142
|
+
sub_pattern.resolved_type = disambiguated
|
|
1143
|
+
sub_pattern.is_union_field_guard = True
|
|
1144
|
+
return disambiguated
|
|
1145
|
+
raise self.ctx.error(
|
|
1146
|
+
f"ambiguous '{cls_name}' in field union type '{field_type}'",
|
|
1147
|
+
sub_pattern,
|
|
1148
|
+
)
|
|
1149
|
+
|
|
1150
|
+
raise self.ctx.error(
|
|
1151
|
+
f"type '{cls_name}' is not a member of "
|
|
1152
|
+
f"field union type '{field_type}'", sub_pattern
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
raise self.ctx.error(
|
|
1156
|
+
f"type pattern '{cls_name}' does not match "
|
|
1157
|
+
f"field type '{field_type}'", sub_pattern
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
def _resolve_nested_class_fields(
|
|
1161
|
+
self, sub_pattern: TpyClassPattern, matched_type: TpyType,
|
|
1162
|
+
bindings: dict[str, TpyType],
|
|
1163
|
+
) -> None:
|
|
1164
|
+
"""Recursively resolve field patterns on a nested class sub-pattern."""
|
|
1165
|
+
if not isinstance(sub_pattern.cls, TpyName):
|
|
1166
|
+
return
|
|
1167
|
+
cls_name = sub_pattern.cls.name
|
|
1168
|
+
record = self.ctx.registry.get_record(cls_name)
|
|
1169
|
+
if record is None:
|
|
1170
|
+
raise self.ctx.error(
|
|
1171
|
+
f"type '{cls_name}' does not support field patterns", sub_pattern
|
|
1172
|
+
)
|
|
1173
|
+
# resolved_type is already set by _validate_field_type_pattern for
|
|
1174
|
+
# union fields; for exact matches, set it for type param substitution
|
|
1175
|
+
if sub_pattern.resolved_type is None:
|
|
1176
|
+
sub_pattern.resolved_type = matched_type
|
|
1177
|
+
self._resolve_class_pattern_fields(sub_pattern, record, bindings)
|