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/narrowing.py
ADDED
|
@@ -0,0 +1,857 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Narrowing Tracker
|
|
3
|
+
|
|
4
|
+
Centralizes type narrowing (Optional + Union) and fact invalidation on writes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
from ..typesys import (
|
|
11
|
+
TpyType, OptionalType, NoneType, VoidType, PtrType, OwnType, NominalType, AliasRef,
|
|
12
|
+
TypeParamRef, IntLiteralType, AnyType,
|
|
13
|
+
ReadonlyType, UnionType, unwrap_readonly, unwrap_qualifiers, unwrap_ref_type, make_union, union_none_narrow,
|
|
14
|
+
is_protocol_type, is_dynamic_dispatch_inner, polymorphic_source_inner,
|
|
15
|
+
LiteralType, LiteralValue, is_any_int_type,
|
|
16
|
+
|
|
17
|
+
)
|
|
18
|
+
from ..parse import (
|
|
19
|
+
TpyExpr, TpyName, TpyBinOp, TpyUnaryOp, TpyFieldAccess,
|
|
20
|
+
TpySubscript, TpyNoneLiteral, TpyCall, TpyMethodCall,
|
|
21
|
+
TpyIntLiteral, TpyCoerce, TpyNamedExpr,
|
|
22
|
+
)
|
|
23
|
+
from .literal_utils import literal_value_from_expr
|
|
24
|
+
from .value_range import ValueRange
|
|
25
|
+
from ..prescan import match_is_none, _expr_to_narrowing_key, deref_view_key
|
|
26
|
+
from ..namespace import BindingKind
|
|
27
|
+
from ..diagnostics import OPTIONAL_VALUE_TRUTHINESS_WARNING
|
|
28
|
+
from ..type_def_registry import protocol_info_of
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from .context import SemanticContext
|
|
32
|
+
from .type_ops import TypeOperations
|
|
33
|
+
from .protocols import ProtocolChecker
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class NarrowingTracker:
|
|
37
|
+
"""Centralized type narrowing flow analysis.
|
|
38
|
+
|
|
39
|
+
Operates on ctx.func.narrowed_types without owning it.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(
|
|
43
|
+
self,
|
|
44
|
+
ctx: SemanticContext,
|
|
45
|
+
type_ops: TypeOperations,
|
|
46
|
+
protocols: ProtocolChecker,
|
|
47
|
+
):
|
|
48
|
+
self.ctx = ctx
|
|
49
|
+
self.type_ops = type_ops
|
|
50
|
+
self.protocols = protocols
|
|
51
|
+
|
|
52
|
+
# -- Name-based narrowing -------------------------------------------
|
|
53
|
+
|
|
54
|
+
def narrow_name_type(self, name: str, typ: TpyType) -> TpyType:
|
|
55
|
+
"""Narrow Optional/Union name type using flow facts."""
|
|
56
|
+
return self.ctx.func.narrowed_types.get(name, typ)
|
|
57
|
+
|
|
58
|
+
def declared_type_for_name(self, name: str) -> TpyType | None:
|
|
59
|
+
"""Get a variable's declared type (without applying flow narrowing).
|
|
60
|
+
|
|
61
|
+
Strips Ref -- declared types reflect the user's annotation, not
|
|
62
|
+
the internal reference-provenance wrapper.
|
|
63
|
+
"""
|
|
64
|
+
if self.ctx.func.current_ns:
|
|
65
|
+
binding = self.ctx.func.current_ns.lookup(name)
|
|
66
|
+
if binding and binding.kind == BindingKind.VARIABLE:
|
|
67
|
+
return unwrap_ref_type(binding.type)
|
|
68
|
+
typ = self.ctx.func.current_scope.lookup(name)
|
|
69
|
+
return unwrap_ref_type(typ) if typ is not None else None
|
|
70
|
+
|
|
71
|
+
def _resolve_field_path_type(self, key: str) -> TpyType | None:
|
|
72
|
+
"""Resolve the declared type for a dotted field path like 'obj.field' or 'obj.a.b'."""
|
|
73
|
+
parts = key.split(".")
|
|
74
|
+
if len(parts) < 2:
|
|
75
|
+
return None
|
|
76
|
+
expr: TpyExpr = TpyName(parts[0])
|
|
77
|
+
for field in parts[1:]:
|
|
78
|
+
expr = TpyFieldAccess(expr, field)
|
|
79
|
+
return self.declared_type_for_expr(expr)
|
|
80
|
+
|
|
81
|
+
def effective_union_type(self, name: str) -> TpyType | None:
|
|
82
|
+
"""Get effective type for isinstance/narrowing, falling through assignment narrowing.
|
|
83
|
+
|
|
84
|
+
If the variable is narrowed to a concrete (non-union) type by assignment
|
|
85
|
+
narrowing, returns the declared union type instead, since isinstance and
|
|
86
|
+
condition_type_facts need the full union to compute branch facts.
|
|
87
|
+
"""
|
|
88
|
+
effective = self.ctx.func.narrowed_types.get(name)
|
|
89
|
+
if effective is None:
|
|
90
|
+
effective = self.declared_type_for_name(name)
|
|
91
|
+
# Strip Own[T] -- narrowing operates on the underlying type
|
|
92
|
+
if isinstance(effective, OwnType):
|
|
93
|
+
effective = effective.wrapped
|
|
94
|
+
if not isinstance(effective, UnionType):
|
|
95
|
+
declared = self.declared_type_for_name(name)
|
|
96
|
+
if declared is not None:
|
|
97
|
+
inner = unwrap_readonly(declared)
|
|
98
|
+
if isinstance(inner, OwnType):
|
|
99
|
+
inner = inner.wrapped
|
|
100
|
+
if isinstance(inner, UnionType):
|
|
101
|
+
return inner
|
|
102
|
+
# Expand recursive union alias placeholder to underlying UnionType
|
|
103
|
+
# (e.g. after Optional narrowing: Tree | None -> Tree -> int | list[Tree]).
|
|
104
|
+
# `AliasRef` is the parser-emitted self-reference inside a
|
|
105
|
+
# recursive alias body; expand by looking the alias up.
|
|
106
|
+
if isinstance(effective, AliasRef):
|
|
107
|
+
alias = self.ctx.registry.get_type_alias(effective.name)
|
|
108
|
+
if alias is not None:
|
|
109
|
+
return alias
|
|
110
|
+
return effective
|
|
111
|
+
|
|
112
|
+
# -- Declared type resolution for expressions ------------------------
|
|
113
|
+
|
|
114
|
+
def declared_type_for_expr(self, expr: TpyExpr) -> TpyType | None:
|
|
115
|
+
"""Get declared type for identity-capable expressions without flow narrowing."""
|
|
116
|
+
if isinstance(expr, TpyName):
|
|
117
|
+
return self.declared_type_for_name(expr.name)
|
|
118
|
+
if isinstance(expr, TpyFieldAccess):
|
|
119
|
+
obj_type = self.declared_type_for_expr(expr.obj)
|
|
120
|
+
if obj_type is None:
|
|
121
|
+
return None
|
|
122
|
+
actual_type = unwrap_readonly(unwrap_ref_type(obj_type))
|
|
123
|
+
if isinstance(actual_type, PtrType):
|
|
124
|
+
actual_type = actual_type.pointee
|
|
125
|
+
elif isinstance(actual_type, OwnType):
|
|
126
|
+
actual_type = actual_type.wrapped
|
|
127
|
+
elif isinstance(actual_type, OptionalType):
|
|
128
|
+
if actual_type.inner.is_value_type():
|
|
129
|
+
return None
|
|
130
|
+
actual_type = actual_type.inner
|
|
131
|
+
|
|
132
|
+
if isinstance(actual_type, NominalType) and actual_type.is_record:
|
|
133
|
+
record = self.ctx.registry.get_record_for_type(actual_type)
|
|
134
|
+
if not record:
|
|
135
|
+
return None
|
|
136
|
+
type_subst = self.type_ops.build_type_substitution(actual_type)
|
|
137
|
+
field_info = self.protocols.lookup_record_field(record, expr.field)
|
|
138
|
+
if field_info is None:
|
|
139
|
+
return None
|
|
140
|
+
field_type = field_info.type
|
|
141
|
+
if type_subst:
|
|
142
|
+
field_type = self.type_ops.substitute_type_params(field_type, type_subst)
|
|
143
|
+
return field_type
|
|
144
|
+
|
|
145
|
+
if isinstance(actual_type, TypeParamRef):
|
|
146
|
+
bound = self.type_ops.get_type_param_bound(actual_type.name)
|
|
147
|
+
if bound is not None and is_protocol_type(bound):
|
|
148
|
+
protocol_info = protocol_info_of(bound)
|
|
149
|
+
if protocol_info:
|
|
150
|
+
for field_name, field_type in protocol_info.fields or []:
|
|
151
|
+
if field_name == expr.field:
|
|
152
|
+
type_subst: dict[str, TpyType] = {"Self": actual_type}
|
|
153
|
+
if protocol_info.type_params and bound.type_args:
|
|
154
|
+
type_subst.update(dict(zip(protocol_info.type_params, bound.type_args)))
|
|
155
|
+
return self.type_ops.substitute_types(field_type, type_subst)
|
|
156
|
+
return None
|
|
157
|
+
if isinstance(expr, TpySubscript):
|
|
158
|
+
obj_type = self.declared_type_for_expr(expr.obj)
|
|
159
|
+
if obj_type is None:
|
|
160
|
+
return None
|
|
161
|
+
actual_type = unwrap_readonly(obj_type)
|
|
162
|
+
if isinstance(actual_type, OptionalType):
|
|
163
|
+
if actual_type.inner.is_value_type():
|
|
164
|
+
return None
|
|
165
|
+
actual_type = actual_type.inner
|
|
166
|
+
elem_type = actual_type.get_element_type()
|
|
167
|
+
if elem_type is not None:
|
|
168
|
+
return elem_type
|
|
169
|
+
if is_protocol_type(actual_type):
|
|
170
|
+
return self._get_protocol_getitem_type(actual_type)
|
|
171
|
+
if isinstance(actual_type, NominalType) and actual_type.is_record:
|
|
172
|
+
return self._get_record_getitem_type(actual_type)
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def _get_protocol_getitem_type(self, protocol: NominalType) -> TpyType | None:
|
|
176
|
+
"""Get __getitem__ return type for a protocol (returns None on failure)."""
|
|
177
|
+
protocol_info = protocol_info_of(protocol)
|
|
178
|
+
if protocol_info is None:
|
|
179
|
+
return None
|
|
180
|
+
type_subst: dict[str, TpyType] = {}
|
|
181
|
+
if protocol_info.type_params and protocol.type_args:
|
|
182
|
+
type_subst = dict(zip(protocol_info.type_params, protocol.type_args))
|
|
183
|
+
for method_sig in protocol_info.methods:
|
|
184
|
+
if method_sig.name == "__getitem__":
|
|
185
|
+
if type_subst:
|
|
186
|
+
return self.type_ops.substitute_types(method_sig.return_type, type_subst)
|
|
187
|
+
return method_sig.return_type
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def _get_record_getitem_type(self, record_type: NominalType) -> TpyType | None:
|
|
191
|
+
"""Get __getitem__ return type for a record (returns None on failure)."""
|
|
192
|
+
record = self.ctx.registry.get_record_for_type(record_type)
|
|
193
|
+
if record is None:
|
|
194
|
+
return None
|
|
195
|
+
getitem = self.protocols.lookup_record_method(record, "__getitem__")
|
|
196
|
+
if getitem is None:
|
|
197
|
+
return None
|
|
198
|
+
type_subst = self.type_ops.build_type_substitution(record_type)
|
|
199
|
+
if type_subst:
|
|
200
|
+
return self.type_ops.substitute_type_params(getitem.return_type, type_subst)
|
|
201
|
+
return getitem.return_type
|
|
202
|
+
|
|
203
|
+
# -- Condition fact extraction --------------------------------------
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def _is_optional_type(typ: TpyType | None) -> bool:
|
|
207
|
+
"""Check if type is Optional (possibly wrapped in ReadonlyType)."""
|
|
208
|
+
if typ is None:
|
|
209
|
+
return False
|
|
210
|
+
if isinstance(typ, ReadonlyType):
|
|
211
|
+
typ = typ.wrapped
|
|
212
|
+
return isinstance(typ, OptionalType)
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _optional_inner_type(typ: TpyType) -> TpyType:
|
|
216
|
+
"""Extract inner type from Optional, preserving ReadonlyType/OwnType wrappers."""
|
|
217
|
+
if isinstance(typ, ReadonlyType):
|
|
218
|
+
return ReadonlyType(typ.wrapped.inner)
|
|
219
|
+
if isinstance(typ, OwnType):
|
|
220
|
+
inner = typ.wrapped
|
|
221
|
+
if isinstance(inner, OptionalType):
|
|
222
|
+
return inner.inner
|
|
223
|
+
return inner
|
|
224
|
+
return typ.inner
|
|
225
|
+
|
|
226
|
+
def _effective_type_for_key(self, key: str) -> TpyType | None:
|
|
227
|
+
"""Get the effective type for a narrowing key (name or dotted path)."""
|
|
228
|
+
effective = self.ctx.func.narrowed_types.get(key)
|
|
229
|
+
if effective is not None:
|
|
230
|
+
return effective
|
|
231
|
+
if "." in key:
|
|
232
|
+
return self._resolve_field_path_type(key)
|
|
233
|
+
result = self.declared_type_for_name(key)
|
|
234
|
+
# Strip Own[T] -- narrowing operates on the underlying type
|
|
235
|
+
if isinstance(result, OwnType):
|
|
236
|
+
result = result.wrapped
|
|
237
|
+
return result
|
|
238
|
+
|
|
239
|
+
# -- Type narrowing (isinstance, is None, truthiness) ---------------
|
|
240
|
+
|
|
241
|
+
def _isinstance_facts(
|
|
242
|
+
self, expr: TpyExpr,
|
|
243
|
+
) -> tuple[dict[str, TpyType], dict[str, TpyType]]:
|
|
244
|
+
"""Extract (true_facts, false_facts) for type narrowing.
|
|
245
|
+
|
|
246
|
+
Handles isinstance checks (union), is/is not None (union + optional),
|
|
247
|
+
and truthiness (optional).
|
|
248
|
+
"""
|
|
249
|
+
if isinstance(expr, TpyCall) and expr.isinstance_var is not None and expr.isinstance_type is not None:
|
|
250
|
+
name = expr.isinstance_var
|
|
251
|
+
check_type = expr.isinstance_type
|
|
252
|
+
# Deref-view narrowing (owning wrapper source): narrow the wrapper's
|
|
253
|
+
# polymorphic payload under a distinct key, leaving the wrapper
|
|
254
|
+
# variable's own type intact. The false branch can't narrow the
|
|
255
|
+
# payload (it might be any other subclass).
|
|
256
|
+
if expr.isinstance_deref_depth > 0:
|
|
257
|
+
return {deref_view_key(name): check_type}, {}
|
|
258
|
+
effective = self.effective_union_type(name)
|
|
259
|
+
# Any narrowing (D15): non-consuming -- the true branch sees the
|
|
260
|
+
# variable as `check_type` (a borrow into the cell); the false
|
|
261
|
+
# branch keeps the original Any (no narrowing). The outer cell
|
|
262
|
+
# remains alive in both branches.
|
|
263
|
+
if isinstance(effective, AnyType):
|
|
264
|
+
return {name: check_type}, {}
|
|
265
|
+
if isinstance(effective, UnionType):
|
|
266
|
+
# Tuple form isinstance(x, (A, B)) packs check types as a union.
|
|
267
|
+
check_members = (tuple(check_type.members)
|
|
268
|
+
if isinstance(check_type, UnionType)
|
|
269
|
+
else (check_type,))
|
|
270
|
+
remaining = [m for m in effective.members if m not in check_members]
|
|
271
|
+
false_type = (make_union(*remaining) if remaining else check_type)
|
|
272
|
+
return {name: check_type}, {name: false_type}
|
|
273
|
+
# Optional[Protocol] isinstance narrows to the inner protocol type.
|
|
274
|
+
# Exception: a @dynamic protocol inner with a concrete-class check
|
|
275
|
+
# type is a runtime subclass dispatch -- fall through to the
|
|
276
|
+
# polymorphic branch below so it narrows to the subclass (Dog),
|
|
277
|
+
# not the bare protocol (Pet).
|
|
278
|
+
if self._is_optional_type(effective):
|
|
279
|
+
inner = self._optional_inner_type(effective)
|
|
280
|
+
if is_protocol_type(inner) and not (
|
|
281
|
+
is_dynamic_dispatch_inner(inner, self.ctx.registry)
|
|
282
|
+
and isinstance(check_type, NominalType)
|
|
283
|
+
and not is_protocol_type(check_type)):
|
|
284
|
+
return {name: inner}, {}
|
|
285
|
+
# Optional[Polymorphic class] or post-`is not None`-narrowed
|
|
286
|
+
# polymorphic class: isinstance(x, Subclass) narrows to Subclass.
|
|
287
|
+
# Codegen emits a dynamic_cast'd reference at branch entry and
|
|
288
|
+
# routes downstream reads through it (cast-and-cache).
|
|
289
|
+
if (polymorphic_source_inner(effective, self.ctx.registry) is not None
|
|
290
|
+
and isinstance(check_type, NominalType)):
|
|
291
|
+
return {name: check_type}, {}
|
|
292
|
+
# Bare-protocol isinstance: narrowing to a *child* protocol
|
|
293
|
+
# (e.g. Iterable[T] -> NativeIterable[T]) lets downstream codegen
|
|
294
|
+
# (for-loop dispatch, `in` operator) see the refined protocol via
|
|
295
|
+
# protocol_narrowings. Only narrow when the check protocol
|
|
296
|
+
# inherits from the declared one -- otherwise the source's
|
|
297
|
+
# interface is lost (e.g. Sequence[T] -> Hashable would drop
|
|
298
|
+
# __len__ from sema's view, even though both constraints hold
|
|
299
|
+
# in the C++ `if constexpr` branch).
|
|
300
|
+
if (expr.isinstance_is_protocol
|
|
301
|
+
and is_protocol_type(effective) and is_protocol_type(check_type)
|
|
302
|
+
and self.protocols.protocol_inherits_from(
|
|
303
|
+
check_type.name, effective.name)):
|
|
304
|
+
return {name: check_type}, {}
|
|
305
|
+
|
|
306
|
+
# is None / is not None on union or optional types
|
|
307
|
+
match = match_is_none(expr)
|
|
308
|
+
if match is not None:
|
|
309
|
+
key, is_not_none = match
|
|
310
|
+
effective = self._effective_type_for_key(key)
|
|
311
|
+
if isinstance(effective, UnionType) and effective.has_none_member():
|
|
312
|
+
non_none_type, none_type = union_none_narrow(effective)
|
|
313
|
+
if is_not_none:
|
|
314
|
+
return {key: non_none_type}, {key: none_type}
|
|
315
|
+
else:
|
|
316
|
+
return {key: none_type}, {key: non_none_type}
|
|
317
|
+
if self._is_optional_type(effective):
|
|
318
|
+
inner_type = self._optional_inner_type(effective)
|
|
319
|
+
if is_not_none:
|
|
320
|
+
return {key: inner_type}, {}
|
|
321
|
+
else:
|
|
322
|
+
return {}, {key: inner_type}
|
|
323
|
+
|
|
324
|
+
# Truthiness on Optional: `if x:` / `if obj.field:` / `if (x := get()):` narrows
|
|
325
|
+
if isinstance(expr, (TpyName, TpyFieldAccess, TpyNamedExpr)):
|
|
326
|
+
key = _expr_to_narrowing_key(expr)
|
|
327
|
+
if key is not None:
|
|
328
|
+
effective = self._effective_type_for_key(key)
|
|
329
|
+
if self._is_optional_type(effective):
|
|
330
|
+
inner_type = self._optional_inner_type(effective)
|
|
331
|
+
return {key: inner_type}, {}
|
|
332
|
+
|
|
333
|
+
# Equality narrowing on Literal types: `if mode == "rb":`
|
|
334
|
+
if isinstance(expr, TpyBinOp) and expr.op in ("==", "!="):
|
|
335
|
+
facts = self._literal_equality_facts(expr)
|
|
336
|
+
if facts is not None:
|
|
337
|
+
return facts
|
|
338
|
+
|
|
339
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "!":
|
|
340
|
+
true_facts, false_facts = self._isinstance_facts(expr.operand)
|
|
341
|
+
return false_facts, true_facts
|
|
342
|
+
|
|
343
|
+
if isinstance(expr, TpyBinOp):
|
|
344
|
+
if expr.op == "&&":
|
|
345
|
+
left_true, left_false = self._isinstance_facts(expr.left)
|
|
346
|
+
right_true, right_false = self._isinstance_facts(expr.right)
|
|
347
|
+
merged_true = self._merge_facts_both_hold(left_true, right_true)
|
|
348
|
+
merged_false = {k: v for k, v in left_false.items()
|
|
349
|
+
if k in right_false and right_false[k] == v}
|
|
350
|
+
return merged_true, merged_false
|
|
351
|
+
if expr.op == "||":
|
|
352
|
+
left_true, left_false = self._isinstance_facts(expr.left)
|
|
353
|
+
right_true, right_false = self._isinstance_facts(expr.right)
|
|
354
|
+
merged_true = self._merge_facts_either_holds(left_true, right_true)
|
|
355
|
+
merged_false = self._merge_facts_both_hold(left_false, right_false)
|
|
356
|
+
return merged_true, merged_false
|
|
357
|
+
|
|
358
|
+
return {}, {}
|
|
359
|
+
|
|
360
|
+
@staticmethod
|
|
361
|
+
def _merge_facts_either_holds(
|
|
362
|
+
left: dict[str, TpyType], right: dict[str, TpyType],
|
|
363
|
+
) -> dict[str, TpyType]:
|
|
364
|
+
"""Merge facts where at least one holds (||-true, &&-false).
|
|
365
|
+
|
|
366
|
+
For same key with LiteralType on both sides, union the value sets.
|
|
367
|
+
For same key with non-Literal types, keep only if they agree exactly.
|
|
368
|
+
Keys present in only one side are dropped (can't assume which held).
|
|
369
|
+
"""
|
|
370
|
+
merged = {}
|
|
371
|
+
for k, lv in left.items():
|
|
372
|
+
if k not in right:
|
|
373
|
+
continue
|
|
374
|
+
rv = right[k]
|
|
375
|
+
if (isinstance(lv, LiteralType) and isinstance(rv, LiteralType)
|
|
376
|
+
and lv.base_type == rv.base_type):
|
|
377
|
+
seen = set(lv.values)
|
|
378
|
+
combined = list(lv.values) + [v for v in rv.values if v not in seen]
|
|
379
|
+
merged[k] = LiteralType(base_type=lv.base_type, values=tuple(combined))
|
|
380
|
+
elif lv == rv:
|
|
381
|
+
merged[k] = lv
|
|
382
|
+
return merged
|
|
383
|
+
|
|
384
|
+
@staticmethod
|
|
385
|
+
def _merge_facts_both_hold(
|
|
386
|
+
left: dict[str, TpyType], right: dict[str, TpyType],
|
|
387
|
+
) -> dict[str, TpyType]:
|
|
388
|
+
"""Merge facts where both must hold (&&-true, ||-false).
|
|
389
|
+
|
|
390
|
+
For distinct keys, include both. For same key with LiteralType on
|
|
391
|
+
both sides, intersect the value sets. For non-Literal same-key
|
|
392
|
+
conflicts, right overrides left (preserves pre-existing behavior
|
|
393
|
+
where isinstance narrows further after is-not-None).
|
|
394
|
+
"""
|
|
395
|
+
merged = dict(left)
|
|
396
|
+
for k, rv in right.items():
|
|
397
|
+
if k not in merged:
|
|
398
|
+
merged[k] = rv
|
|
399
|
+
continue
|
|
400
|
+
lv = merged[k]
|
|
401
|
+
if (isinstance(lv, LiteralType) and isinstance(rv, LiteralType)
|
|
402
|
+
and lv.base_type == rv.base_type):
|
|
403
|
+
common = tuple(v for v in lv.values if v in rv.values)
|
|
404
|
+
if common:
|
|
405
|
+
merged[k] = LiteralType(base_type=lv.base_type, values=common)
|
|
406
|
+
else:
|
|
407
|
+
merged[k] = lv.base_type
|
|
408
|
+
else:
|
|
409
|
+
merged[k] = rv
|
|
410
|
+
return merged
|
|
411
|
+
|
|
412
|
+
def _literal_equality_facts(
|
|
413
|
+
self, expr: TpyBinOp,
|
|
414
|
+
) -> tuple[dict[str, TpyType], dict[str, TpyType]] | None:
|
|
415
|
+
"""Extract narrowing facts for `x == lit` / `lit == x` on LiteralType vars."""
|
|
416
|
+
key: str | None = None
|
|
417
|
+
lit_val: LiteralValue | None = None
|
|
418
|
+
# Try both directions: `x == "rb"` and `"rb" == x`
|
|
419
|
+
for var_side, lit_side in [(expr.left, expr.right), (expr.right, expr.left)]:
|
|
420
|
+
k = _expr_to_narrowing_key(var_side)
|
|
421
|
+
if k is None:
|
|
422
|
+
continue
|
|
423
|
+
lv = literal_value_from_expr(lit_side)
|
|
424
|
+
if lv is not None:
|
|
425
|
+
key, lit_val = k, lv
|
|
426
|
+
break
|
|
427
|
+
if key is None or lit_val is None:
|
|
428
|
+
return None
|
|
429
|
+
effective = self._effective_type_for_key(key)
|
|
430
|
+
if not isinstance(effective, LiteralType):
|
|
431
|
+
return None
|
|
432
|
+
if lit_val not in effective.values:
|
|
433
|
+
return None
|
|
434
|
+
true_type = LiteralType(base_type=effective.base_type, values=(lit_val,))
|
|
435
|
+
remaining = tuple(v for v in effective.values if v != lit_val)
|
|
436
|
+
false_type = (LiteralType(base_type=effective.base_type, values=remaining)
|
|
437
|
+
if remaining else effective.base_type)
|
|
438
|
+
if expr.op == "==":
|
|
439
|
+
return {key: true_type}, {key: false_type}
|
|
440
|
+
else:
|
|
441
|
+
return {key: false_type}, {key: true_type}
|
|
442
|
+
|
|
443
|
+
def condition_type_facts(
|
|
444
|
+
self, condition: TpyExpr,
|
|
445
|
+
) -> tuple[dict[str, TpyType], dict[str, TpyType]]:
|
|
446
|
+
"""Get (true_facts, false_facts) for type narrowing (isinstance, is None, truthiness)."""
|
|
447
|
+
return self._isinstance_facts(condition)
|
|
448
|
+
|
|
449
|
+
def condition_ptr_null_facts(
|
|
450
|
+
self, condition: TpyExpr,
|
|
451
|
+
) -> tuple[set[str], set[str]]:
|
|
452
|
+
"""Get (true_nonnull, false_nonnull) for Ptr is/is not None checks.
|
|
453
|
+
|
|
454
|
+
Returns names proven non-null in the true branch and names proven
|
|
455
|
+
non-null in the false branch, respectively. When `p is not None`
|
|
456
|
+
and p is a Ptr[T], returns ({p}, {}) -- p is non-null when the
|
|
457
|
+
condition is true. When `p is None`, returns ({}, {p}) -- p is
|
|
458
|
+
non-null when the condition is false.
|
|
459
|
+
"""
|
|
460
|
+
return self._ptr_null_facts(condition)
|
|
461
|
+
|
|
462
|
+
def _ptr_null_facts(
|
|
463
|
+
self, expr: TpyExpr,
|
|
464
|
+
) -> tuple[set[str], set[str]]:
|
|
465
|
+
match = match_is_none(expr)
|
|
466
|
+
if match is not None:
|
|
467
|
+
key, is_not_none = match
|
|
468
|
+
if "." in key:
|
|
469
|
+
declared = self._resolve_field_path_type(key)
|
|
470
|
+
else:
|
|
471
|
+
declared = self.declared_type_for_name(key)
|
|
472
|
+
if isinstance(declared, PtrType):
|
|
473
|
+
if is_not_none:
|
|
474
|
+
return {key}, set()
|
|
475
|
+
else:
|
|
476
|
+
return set(), {key}
|
|
477
|
+
|
|
478
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "!":
|
|
479
|
+
non_null, null = self._ptr_null_facts(expr.operand)
|
|
480
|
+
return null, non_null
|
|
481
|
+
|
|
482
|
+
if isinstance(expr, TpyBinOp):
|
|
483
|
+
if expr.op == "&&":
|
|
484
|
+
l_nn, l_n = self._ptr_null_facts(expr.left)
|
|
485
|
+
r_nn, r_n = self._ptr_null_facts(expr.right)
|
|
486
|
+
return l_nn | r_nn, l_n & r_n
|
|
487
|
+
if expr.op == "||":
|
|
488
|
+
l_nn, l_n = self._ptr_null_facts(expr.left)
|
|
489
|
+
r_nn, r_n = self._ptr_null_facts(expr.right)
|
|
490
|
+
return l_nn & r_nn, l_n | r_n
|
|
491
|
+
|
|
492
|
+
return set(), set()
|
|
493
|
+
|
|
494
|
+
# -- Integer range facts from conditions ----------------------------
|
|
495
|
+
|
|
496
|
+
def condition_range_facts(
|
|
497
|
+
self, condition: TpyExpr,
|
|
498
|
+
) -> tuple[dict[str, ValueRange], dict[str, ValueRange]]:
|
|
499
|
+
"""Get (true_facts, false_facts) for integer range narrowing.
|
|
500
|
+
|
|
501
|
+
Handles comparisons (!=, ==, <, <=, >, >=), including symbolic
|
|
502
|
+
len() comparisons, and logical composition (and/or/not).
|
|
503
|
+
"""
|
|
504
|
+
return self._range_facts(condition)
|
|
505
|
+
|
|
506
|
+
def _range_facts(
|
|
507
|
+
self, expr: TpyExpr,
|
|
508
|
+
) -> tuple[dict[str, ValueRange], dict[str, ValueRange]]:
|
|
509
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "!":
|
|
510
|
+
true_facts, false_facts = self._range_facts(expr.operand)
|
|
511
|
+
return false_facts, true_facts
|
|
512
|
+
|
|
513
|
+
if isinstance(expr, TpyBinOp):
|
|
514
|
+
if expr.op == "&&":
|
|
515
|
+
l_true, l_false = self._range_facts(expr.left)
|
|
516
|
+
r_true, r_false = self._range_facts(expr.right)
|
|
517
|
+
merged_true = _intersect_range_dicts(l_true, r_true)
|
|
518
|
+
merged_false = _merge_range_dicts(l_false, r_false)
|
|
519
|
+
return merged_true, merged_false
|
|
520
|
+
if expr.op == "||":
|
|
521
|
+
l_true, l_false = self._range_facts(expr.left)
|
|
522
|
+
r_true, r_false = self._range_facts(expr.right)
|
|
523
|
+
merged_true = _merge_range_dicts(l_true, r_true)
|
|
524
|
+
merged_false = _intersect_range_dicts(l_false, r_false)
|
|
525
|
+
return merged_true, merged_false
|
|
526
|
+
|
|
527
|
+
return self._comparison_range_facts(expr)
|
|
528
|
+
|
|
529
|
+
return {}, {}
|
|
530
|
+
|
|
531
|
+
def _is_integer_typed(self, expr: TpyExpr) -> bool:
|
|
532
|
+
"""Check if an expression has an integer type (fixed-width or BigInt)."""
|
|
533
|
+
typ = self.ctx.get_expr_type(expr)
|
|
534
|
+
if typ is None:
|
|
535
|
+
return False
|
|
536
|
+
return is_any_int_type(typ)
|
|
537
|
+
|
|
538
|
+
def _comparison_range_facts(
|
|
539
|
+
self, expr: TpyBinOp,
|
|
540
|
+
) -> tuple[dict[str, ValueRange], dict[str, ValueRange]]:
|
|
541
|
+
"""Extract range facts from a single comparison (==, !=, <, <=, >, >=)."""
|
|
542
|
+
op = expr.op
|
|
543
|
+
if op not in ("==", "!=", "<", "<=", ">", ">="):
|
|
544
|
+
return {}, {}
|
|
545
|
+
|
|
546
|
+
left, right = expr.left, expr.right
|
|
547
|
+
|
|
548
|
+
# Normalize: put the variable name on the left side
|
|
549
|
+
name: str | None = None
|
|
550
|
+
other: TpyExpr | None = None
|
|
551
|
+
|
|
552
|
+
if isinstance(left, TpyName) and self._is_integer_typed(left):
|
|
553
|
+
name = left.name
|
|
554
|
+
other = right
|
|
555
|
+
elif isinstance(right, TpyName) and self._is_integer_typed(right):
|
|
556
|
+
name = right.name
|
|
557
|
+
other = left
|
|
558
|
+
# Flip the operator: x < 5 becomes 5 > x
|
|
559
|
+
op = {"<": ">", "<=": ">=", ">": "<", ">=": "<=",
|
|
560
|
+
"==": "==", "!=": "!="}[op]
|
|
561
|
+
|
|
562
|
+
if name is None or other is None:
|
|
563
|
+
return {}, {}
|
|
564
|
+
|
|
565
|
+
# Determine what 'other' is: literal, len() call, or unknown
|
|
566
|
+
literal_val = self._extract_int_literal(other)
|
|
567
|
+
len_of = self._extract_len_of(other)
|
|
568
|
+
|
|
569
|
+
existing = self.ctx.func.value_ranges.get(name, ValueRange())
|
|
570
|
+
|
|
571
|
+
if op == "!=":
|
|
572
|
+
if literal_val == 0:
|
|
573
|
+
return {name: existing.with_non_zero()}, {}
|
|
574
|
+
return {}, {}
|
|
575
|
+
|
|
576
|
+
if op == "==":
|
|
577
|
+
if literal_val is not None:
|
|
578
|
+
false_facts: dict[str, ValueRange] = {}
|
|
579
|
+
if literal_val == 0:
|
|
580
|
+
false_facts = {name: existing.with_non_zero()}
|
|
581
|
+
return {name: ValueRange.from_literal(literal_val)}, false_facts
|
|
582
|
+
return {}, {}
|
|
583
|
+
|
|
584
|
+
if op == ">":
|
|
585
|
+
if literal_val is not None:
|
|
586
|
+
nz = literal_val >= 0
|
|
587
|
+
r = existing.with_lo(literal_val + 1)
|
|
588
|
+
if nz:
|
|
589
|
+
r = r.with_non_zero()
|
|
590
|
+
return {name: r}, {name: existing.with_hi(literal_val)}
|
|
591
|
+
return {}, {}
|
|
592
|
+
|
|
593
|
+
if op == ">=":
|
|
594
|
+
if literal_val is not None:
|
|
595
|
+
nz = literal_val > 0
|
|
596
|
+
r = existing.with_lo(literal_val)
|
|
597
|
+
if nz:
|
|
598
|
+
r = r.with_non_zero()
|
|
599
|
+
return {name: r}, {name: existing.with_hi(literal_val - 1)}
|
|
600
|
+
return {}, {}
|
|
601
|
+
|
|
602
|
+
if op == "<":
|
|
603
|
+
if literal_val is not None:
|
|
604
|
+
return (
|
|
605
|
+
{name: existing.with_hi(literal_val - 1)},
|
|
606
|
+
{name: existing.with_lo(literal_val)},
|
|
607
|
+
)
|
|
608
|
+
if len_of is not None:
|
|
609
|
+
return (
|
|
610
|
+
{name: existing.with_hi_len_of(len_of)},
|
|
611
|
+
{},
|
|
612
|
+
)
|
|
613
|
+
return {}, {}
|
|
614
|
+
|
|
615
|
+
if op == "<=":
|
|
616
|
+
if literal_val is not None:
|
|
617
|
+
return (
|
|
618
|
+
{name: existing.with_hi(literal_val)},
|
|
619
|
+
{name: existing.with_lo(literal_val + 1)},
|
|
620
|
+
)
|
|
621
|
+
return {}, {}
|
|
622
|
+
|
|
623
|
+
return {}, {}
|
|
624
|
+
|
|
625
|
+
def _extract_int_literal(self, expr: TpyExpr) -> int | None:
|
|
626
|
+
"""Extract a concrete integer value from an expression."""
|
|
627
|
+
if isinstance(expr, TpyIntLiteral):
|
|
628
|
+
return expr.value
|
|
629
|
+
return None
|
|
630
|
+
|
|
631
|
+
def _extract_len_of(self, expr: TpyExpr) -> str | None:
|
|
632
|
+
"""Extract container name from len(container) call."""
|
|
633
|
+
if (isinstance(expr, TpyCall) and expr.func_name == "len"
|
|
634
|
+
and len(expr.args) == 1 and isinstance(expr.args[0], TpyName)):
|
|
635
|
+
return expr.args[0].name
|
|
636
|
+
return None
|
|
637
|
+
|
|
638
|
+
# -- Truthiness warnings -------------------------------------------
|
|
639
|
+
|
|
640
|
+
def _truthy_names(self, expr: TpyExpr) -> set[str]:
|
|
641
|
+
"""Collect Optional variable/field keys used in truthiness contexts."""
|
|
642
|
+
if isinstance(expr, TpyName):
|
|
643
|
+
declared = self.declared_type_for_name(expr.name)
|
|
644
|
+
if self._is_optional_type(declared):
|
|
645
|
+
return {expr.name}
|
|
646
|
+
return set()
|
|
647
|
+
if isinstance(expr, TpyFieldAccess):
|
|
648
|
+
key = _expr_to_narrowing_key(expr)
|
|
649
|
+
if key is not None:
|
|
650
|
+
declared = self._resolve_field_path_type(key)
|
|
651
|
+
if self._is_optional_type(declared):
|
|
652
|
+
return {key}
|
|
653
|
+
return set()
|
|
654
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "!":
|
|
655
|
+
return self._truthy_names(expr.operand)
|
|
656
|
+
if isinstance(expr, TpyBinOp) and expr.op in ("&&", "||"):
|
|
657
|
+
return self._truthy_names(expr.left) | self._truthy_names(expr.right)
|
|
658
|
+
return set()
|
|
659
|
+
|
|
660
|
+
def condition_truthy_value_optional_names(self, condition: TpyExpr) -> set[str]:
|
|
661
|
+
"""Get value-optionals used via truthiness in a condition."""
|
|
662
|
+
keys = self._truthy_names(condition)
|
|
663
|
+
result: set[str] = set()
|
|
664
|
+
for key in keys:
|
|
665
|
+
if "." in key:
|
|
666
|
+
declared = self._resolve_field_path_type(key)
|
|
667
|
+
else:
|
|
668
|
+
declared = self.declared_type_for_name(key)
|
|
669
|
+
inner = unwrap_readonly(declared) if declared else None
|
|
670
|
+
if isinstance(inner, OptionalType) and inner.inner.is_value_type():
|
|
671
|
+
result.add(key)
|
|
672
|
+
return result
|
|
673
|
+
|
|
674
|
+
def warn_truthy_value_optionals(self, condition: TpyExpr) -> None:
|
|
675
|
+
"""Warn when truthiness narrows optional value-typed variables."""
|
|
676
|
+
names = self.condition_truthy_value_optional_names(condition)
|
|
677
|
+
for name in sorted(names):
|
|
678
|
+
self.ctx.warning(
|
|
679
|
+
f"{OPTIONAL_VALUE_TRUTHINESS_WARNING} (variable '{name}')",
|
|
680
|
+
condition,
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
# -- Fact invalidation on writes/calls -----------------------------
|
|
684
|
+
|
|
685
|
+
def update_after_write(
|
|
686
|
+
self,
|
|
687
|
+
name: str,
|
|
688
|
+
target_type: TpyType,
|
|
689
|
+
rhs_type: TpyType | None = None,
|
|
690
|
+
rhs_expr: TpyExpr | None = None,
|
|
691
|
+
) -> None:
|
|
692
|
+
"""Update flow facts after assigning/writing a variable."""
|
|
693
|
+
self.ctx.func.narrowed_types.pop(name, None)
|
|
694
|
+
# A deref-view narrowing (isinstance through an owning wrapper) is keyed
|
|
695
|
+
# apart from the variable's own narrowing, so the pop above misses it;
|
|
696
|
+
# clear it too or a reassigned wrapper keeps the stale subclass cast.
|
|
697
|
+
self.ctx.func.narrowed_types.pop(deref_view_key(name), None)
|
|
698
|
+
# Invalidate field narrowing facts rooted at this variable
|
|
699
|
+
self._invalidate_field_facts(name)
|
|
700
|
+
# Invalidate integer range facts for this variable
|
|
701
|
+
self.ctx.func.value_ranges.pop(name, None)
|
|
702
|
+
# Invalidate symbolic bounds referencing this variable's length
|
|
703
|
+
self._invalidate_len_ranges(name)
|
|
704
|
+
# Set range fact for integer literal assignments (e.g. i = 0, i: Int32 = 0).
|
|
705
|
+
# Unwrap one level of TpyCoerce (typed annotations wrap the literal in a coerce node).
|
|
706
|
+
rhs_inner = rhs_expr.expr if isinstance(rhs_expr, TpyCoerce) else rhs_expr
|
|
707
|
+
if isinstance(rhs_inner, TpyIntLiteral):
|
|
708
|
+
self.ctx.func.value_ranges[name] = ValueRange.from_literal(rhs_inner.value)
|
|
709
|
+
# For Optional targets, re-narrow if RHS is provably non-None
|
|
710
|
+
inner_target = unwrap_readonly(target_type)
|
|
711
|
+
if isinstance(inner_target, OwnType):
|
|
712
|
+
inner_target = inner_target.wrapped
|
|
713
|
+
if not isinstance(inner_target, OptionalType):
|
|
714
|
+
return
|
|
715
|
+
if rhs_type is None:
|
|
716
|
+
return
|
|
717
|
+
# Peel readonly/own/ref wrappers on the RHS to mirror the LHS unwrap
|
|
718
|
+
# above -- a readonly Optional field read produces an Optional under
|
|
719
|
+
# a wrapper, and that's still possibly-None.
|
|
720
|
+
rhs_unwrapped = unwrap_qualifiers(rhs_type)
|
|
721
|
+
if isinstance(rhs_unwrapped, (NoneType, OptionalType)) or isinstance(rhs_expr, TpyNoneLiteral):
|
|
722
|
+
return
|
|
723
|
+
self.ctx.func.narrowed_types[name] = self._optional_inner_type(target_type)
|
|
724
|
+
|
|
725
|
+
def _invalidate_field_facts(self, name: str) -> None:
|
|
726
|
+
"""Remove all field narrowing facts rooted at the given variable name."""
|
|
727
|
+
prefix = name + "."
|
|
728
|
+
stale = [k for k in self.ctx.func.narrowed_types if k.startswith(prefix)]
|
|
729
|
+
for k in stale:
|
|
730
|
+
del self.ctx.func.narrowed_types[k]
|
|
731
|
+
stale_ptr = [k for k in self.ctx.func.non_null_ptr_vars if k.startswith(prefix)]
|
|
732
|
+
for k in stale_ptr:
|
|
733
|
+
self.ctx.func.non_null_ptr_vars.discard(k)
|
|
734
|
+
|
|
735
|
+
def invalidate_for_field_write(self, target: TpyExpr) -> None:
|
|
736
|
+
"""Invalidate narrowing facts for sub-paths when a field is written.
|
|
737
|
+
|
|
738
|
+
When `obj.inner = ...` is written, clears Optional narrowing facts for
|
|
739
|
+
deeper paths like `obj.inner.value` (the new object may have different
|
|
740
|
+
field values). Does NOT clear the Optional fact for the written field
|
|
741
|
+
itself -- that is managed by the enclosing `is not None` guard.
|
|
742
|
+
Ptr non-null facts ARE cleared for the written key itself, since a
|
|
743
|
+
field write always introduces a potentially-null pointer value.
|
|
744
|
+
"""
|
|
745
|
+
key = _expr_to_narrowing_key(target)
|
|
746
|
+
if key is None or "." not in key:
|
|
747
|
+
return
|
|
748
|
+
prefix = key + "."
|
|
749
|
+
stale = [k for k in self.ctx.func.narrowed_types if k.startswith(prefix)]
|
|
750
|
+
for k in stale:
|
|
751
|
+
del self.ctx.func.narrowed_types[k]
|
|
752
|
+
self.ctx.func.non_null_ptr_vars.discard(key)
|
|
753
|
+
stale_ptr = [k for k in self.ctx.func.non_null_ptr_vars if k.startswith(prefix)]
|
|
754
|
+
for k in stale_ptr:
|
|
755
|
+
self.ctx.func.non_null_ptr_vars.discard(k)
|
|
756
|
+
|
|
757
|
+
def _invalidate_len_ranges(self, name: str) -> None:
|
|
758
|
+
"""Remove range facts whose symbolic bound references len(name)."""
|
|
759
|
+
stale = [k for k, v in self.ctx.func.value_ranges.items()
|
|
760
|
+
if v.hi_len_of == name]
|
|
761
|
+
for k in stale:
|
|
762
|
+
del self.ctx.func.value_ranges[k]
|
|
763
|
+
|
|
764
|
+
def invalidate_field_facts_for_call(self, call: TpyCall) -> None:
|
|
765
|
+
"""Invalidate field narrowing facts for name arguments passed by mutable reference.
|
|
766
|
+
|
|
767
|
+
When a non-value-type object is passed to a function, the callee receives
|
|
768
|
+
a mutable reference and may modify any field, so field narrowing facts
|
|
769
|
+
for that object are no longer reliable.
|
|
770
|
+
"""
|
|
771
|
+
for arg in call.args:
|
|
772
|
+
if not isinstance(arg, TpyName):
|
|
773
|
+
continue
|
|
774
|
+
arg_type = self.ctx.get_expr_type(arg)
|
|
775
|
+
if arg_type is None:
|
|
776
|
+
continue
|
|
777
|
+
inner = unwrap_readonly(arg_type)
|
|
778
|
+
if inner.is_value_type():
|
|
779
|
+
continue
|
|
780
|
+
self._invalidate_field_facts(arg.name)
|
|
781
|
+
self._invalidate_len_ranges(arg.name)
|
|
782
|
+
|
|
783
|
+
def invalidate_field_facts_for_method_call(self, call: TpyMethodCall) -> None:
|
|
784
|
+
"""Invalidate field narrowing facts after a method call.
|
|
785
|
+
|
|
786
|
+
The receiver object is passed as mutable self, so any field could be mutated.
|
|
787
|
+
Also invalidates symbolic len-based range facts for the receiver (the method
|
|
788
|
+
may change the container's length, e.g. pop/clear/insert).
|
|
789
|
+
Also invalidates for any non-value-type arguments.
|
|
790
|
+
"""
|
|
791
|
+
if not call.is_static_call:
|
|
792
|
+
if isinstance(call.obj, TpyName):
|
|
793
|
+
self._invalidate_field_facts(call.obj.name)
|
|
794
|
+
self._invalidate_len_ranges(call.obj.name)
|
|
795
|
+
else:
|
|
796
|
+
obj_key = _expr_to_narrowing_key(call.obj)
|
|
797
|
+
if obj_key is not None:
|
|
798
|
+
self._invalidate_field_facts(obj_key)
|
|
799
|
+
for arg in call.args:
|
|
800
|
+
if not isinstance(arg, TpyName):
|
|
801
|
+
continue
|
|
802
|
+
arg_type = self.ctx.get_expr_type(arg)
|
|
803
|
+
if arg_type is None:
|
|
804
|
+
continue
|
|
805
|
+
inner = unwrap_readonly(arg_type)
|
|
806
|
+
if inner.is_value_type():
|
|
807
|
+
continue
|
|
808
|
+
self._invalidate_field_facts(arg.name)
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
# -- Module-level helpers for range fact dict operations ------------------
|
|
812
|
+
|
|
813
|
+
def deref_view_narrowed(ctx, obj, deref_target: TpyType) -> 'NominalType | None':
|
|
814
|
+
"""Deref-view narrowed subclass for receiver `obj` when the peeled payload
|
|
815
|
+
`deref_target` is a @dynamic-dispatch inner narrowed by an active
|
|
816
|
+
`if isinstance(<obj>, Sub):`. Shared by the method-call (methods.py) and
|
|
817
|
+
field-access (expressions.py) deref-chain resolvers."""
|
|
818
|
+
inner = unwrap_readonly(deref_target)
|
|
819
|
+
if not (isinstance(inner, NominalType)
|
|
820
|
+
and is_dynamic_dispatch_inner(inner, ctx.registry)):
|
|
821
|
+
return None
|
|
822
|
+
key = _expr_to_narrowing_key(obj)
|
|
823
|
+
if key is None:
|
|
824
|
+
return None
|
|
825
|
+
narrowed = ctx.func.narrowed_types.get(deref_view_key(key))
|
|
826
|
+
return narrowed if isinstance(narrowed, NominalType) else None
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _intersect_range_dicts(
|
|
830
|
+
a: dict[str, ValueRange], b: dict[str, ValueRange],
|
|
831
|
+
) -> dict[str, ValueRange]:
|
|
832
|
+
"""Combine two range fact dicts (and-composition): tighten overlapping keys."""
|
|
833
|
+
if not a:
|
|
834
|
+
return b
|
|
835
|
+
if not b:
|
|
836
|
+
return a
|
|
837
|
+
result = dict(a)
|
|
838
|
+
for k, v in b.items():
|
|
839
|
+
if k in result:
|
|
840
|
+
result[k] = ValueRange.intersect(result[k], v)
|
|
841
|
+
else:
|
|
842
|
+
result[k] = v
|
|
843
|
+
return result
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _merge_range_dicts(
|
|
847
|
+
a: dict[str, ValueRange], b: dict[str, ValueRange],
|
|
848
|
+
) -> dict[str, ValueRange]:
|
|
849
|
+
"""Combine two range fact dicts (or-composition): widen overlapping keys, drop unique."""
|
|
850
|
+
if not a or not b:
|
|
851
|
+
return {}
|
|
852
|
+
result = {}
|
|
853
|
+
for k, v in a.items():
|
|
854
|
+
if k in b:
|
|
855
|
+
result[k] = ValueRange.merge(v, b[k])
|
|
856
|
+
return result
|
|
857
|
+
|