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/parse/nodes.py
ADDED
|
@@ -0,0 +1,1732 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython AST node definitions.
|
|
3
|
+
|
|
4
|
+
Pure data definitions: dataclasses, ParseError, SourceLocation.
|
|
5
|
+
No parsing logic lives here.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
import ast
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum, IntEnum
|
|
12
|
+
from typing import Any, Callable, Literal, Optional, TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from ..typesys import (
|
|
15
|
+
TpyType, NominalType, FieldInfo, FunctionInfo,
|
|
16
|
+
MethodSignature, TypeParamKind, LiteralValue,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Source location for error reporting and source mapping
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class SourceLocation:
|
|
24
|
+
"""Source code location for error reporting and source mapping."""
|
|
25
|
+
line: int # 1-indexed line number
|
|
26
|
+
column: int = 0 # 0-indexed column
|
|
27
|
+
file: str | None = None # Source file path (optional)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ParseError(Exception):
|
|
31
|
+
"""Error during parsing.
|
|
32
|
+
|
|
33
|
+
Accepts either an ast.AST node (for parse-time errors where the ast is
|
|
34
|
+
available) or a SourceLocation (for resolver-time errors where only the
|
|
35
|
+
walked TypeRefNode's loc is known). Lineno is populated from whichever
|
|
36
|
+
is provided.
|
|
37
|
+
"""
|
|
38
|
+
def __init__(self, message: str, node: Optional[ast.AST] = None,
|
|
39
|
+
*, loc: Optional['SourceLocation'] = None):
|
|
40
|
+
self.node = node
|
|
41
|
+
self.message = message
|
|
42
|
+
if node is not None and hasattr(node, 'lineno'):
|
|
43
|
+
self.lineno = node.lineno
|
|
44
|
+
elif loc is not None:
|
|
45
|
+
self.lineno = loc.line
|
|
46
|
+
else:
|
|
47
|
+
self.lineno = None
|
|
48
|
+
loc_str = f" at line {self.lineno}" if self.lineno else ""
|
|
49
|
+
super().__init__(f"{message}{loc_str}")
|
|
50
|
+
|
|
51
|
+
def format(self, filename: str = "<unknown>") -> str:
|
|
52
|
+
"""Format error with file:line prefix."""
|
|
53
|
+
if self.lineno:
|
|
54
|
+
return f"{filename}:{self.lineno}: error: {self.message}"
|
|
55
|
+
return f"{filename}: error: {self.message}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class ResolutionFailure(ParseError):
|
|
59
|
+
"""Recoverable name-resolution failure raised by `TypeResolver`.
|
|
60
|
+
|
|
61
|
+
Signals that a `TpyTypeRef` referred to a name the resolver could not
|
|
62
|
+
bind ("Unknown type", "Unknown generic type", "Unsupported qualified
|
|
63
|
+
type"). Lenient callers (macro-fragment resolution) catch this
|
|
64
|
+
specifically and substitute a `NominalType(name, args)` placeholder;
|
|
65
|
+
everyone else treats it as a regular `ParseError`.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Unresolved type-syntax nodes.
|
|
70
|
+
#
|
|
71
|
+
# Parser emits these in annotation positions instead of constructing TpyType
|
|
72
|
+
# directly. Sema's resolve_type_ref walks them against the TypeDef registry,
|
|
73
|
+
# local type-parameter scope, and structural wrappers to produce TpyType.
|
|
74
|
+
#
|
|
75
|
+
# Four node kinds cover all Python type syntax:
|
|
76
|
+
# - TpyTypeRef : Name or Name[args]. Handles primitives, generics,
|
|
77
|
+
# structural wrappers expressed via subscript
|
|
78
|
+
# (Ptr[T], Own[T], Optional[T], Readonly[T], tuple[T1,T2],
|
|
79
|
+
# Array[T, N], ...), qualified names (Outer.Inner), type
|
|
80
|
+
# parameters, Self, None. Integer args (Array[T, N])
|
|
81
|
+
# sit alongside type args.
|
|
82
|
+
# - TpyUnionRef : T | U | ... (Python BinOp with BitOr).
|
|
83
|
+
# - TpyCallableRef : Callable[[P1, P2], R] or Fn[[P1, P2], R] -- the
|
|
84
|
+
# list-shaped param group doesn't fit a uniform args
|
|
85
|
+
# tuple.
|
|
86
|
+
# - TpyLiteralRef : Literal[v1, v2, ...] where the args are values, not
|
|
87
|
+
# types.
|
|
88
|
+
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class TpyTypeRef:
|
|
91
|
+
"""Named type reference with optional type arguments.
|
|
92
|
+
|
|
93
|
+
`name` is the raw source identifier, possibly dotted for qualified
|
|
94
|
+
references ("Outer.Inner", "module.Name"). Resolution (primitive lookup,
|
|
95
|
+
builtin/user registry lookup, type-parameter substitution, enum/record
|
|
96
|
+
qname minting) happens in sema.
|
|
97
|
+
"""
|
|
98
|
+
name: str
|
|
99
|
+
args: tuple['ResolverInputNode | int', ...] = ()
|
|
100
|
+
loc: SourceLocation | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class TpyUnionRef:
|
|
105
|
+
"""Union-syntax type reference: T | U | ..."""
|
|
106
|
+
members: tuple['ResolverInputNode', ...]
|
|
107
|
+
loc: SourceLocation | None = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class TpyCallableRef:
|
|
112
|
+
"""Callable[[P1, P2], R] or Fn[[P1, P2], R]."""
|
|
113
|
+
kind: Literal["Callable", "Fn"]
|
|
114
|
+
params: tuple['ResolverInputNode', ...]
|
|
115
|
+
return_type: 'ResolverInputNode'
|
|
116
|
+
loc: SourceLocation | None = None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@dataclass(frozen=True)
|
|
120
|
+
class TpyLiteralRef:
|
|
121
|
+
"""Literal[v1, v2, ...] -- values, not types."""
|
|
122
|
+
values: tuple[LiteralValue, ...]
|
|
123
|
+
loc: SourceLocation | None = None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dataclass(frozen=True)
|
|
127
|
+
class TpyInferFromDefaultRef:
|
|
128
|
+
"""Pending marker used by the parser on a FieldInfo whose type could
|
|
129
|
+
not be inferred from its default-value expression at parse time
|
|
130
|
+
(e.g. `Red = auto()` in a class whose `Enum` base was shadowed).
|
|
131
|
+
|
|
132
|
+
Emitted instead of raising "Cannot infer type for field 'X'" at parse
|
|
133
|
+
time so that any sema-time base resolution errors on the enclosing
|
|
134
|
+
record fire first. The post-parse `resolve_refs` pass handles this
|
|
135
|
+
node explicitly in the field loop and surfaces the inference error
|
|
136
|
+
as a SemanticError only when base resolution has already succeeded.
|
|
137
|
+
"""
|
|
138
|
+
loc: SourceLocation | None = None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# ResolverInputNode is the subset of TypeRefNode that TypeResolver.resolve
|
|
142
|
+
# accepts: four parser-walker outputs (named ref, union, callable, literal).
|
|
143
|
+
# TpyInferFromDefaultRef is NOT a resolver input -- it's a field-storage
|
|
144
|
+
# marker that sema catches explicitly before calling the resolver.
|
|
145
|
+
type ResolverInputNode = TpyTypeRef | TpyUnionRef | TpyCallableRef | TpyLiteralRef
|
|
146
|
+
type TypeRefNode = ResolverInputNode | TpyInferFromDefaultRef
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
# AST node types for TurboPython
|
|
150
|
+
|
|
151
|
+
@dataclass
|
|
152
|
+
class TpyExpr:
|
|
153
|
+
"""Base class for expressions."""
|
|
154
|
+
loc: SourceLocation | None = field(default=None, kw_only=True)
|
|
155
|
+
|
|
156
|
+
def children(self) -> list['TpyExpr']:
|
|
157
|
+
"""Return child expression nodes for generic tree walking."""
|
|
158
|
+
return []
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if TYPE_CHECKING:
|
|
162
|
+
from ..coercions import Coercion
|
|
163
|
+
from ..typesys import FunctionInfo, RecordInfo, ResolvedBinop, ResolvedUnaryop
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@dataclass
|
|
167
|
+
class TpyIntLiteral(TpyExpr):
|
|
168
|
+
"""Integer literal."""
|
|
169
|
+
value: int
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class TpyFloatLiteral(TpyExpr):
|
|
174
|
+
"""Floating point literal."""
|
|
175
|
+
value: float
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@dataclass
|
|
179
|
+
class TpyStrLiteral(TpyExpr):
|
|
180
|
+
"""String literal."""
|
|
181
|
+
value: str
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@dataclass
|
|
185
|
+
class TpyBytesLiteral(TpyExpr):
|
|
186
|
+
"""Bytes literal (b"...")."""
|
|
187
|
+
value: bytes
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# F-string conversion codes (from CPython's ast module)
|
|
191
|
+
FSTRING_CONV_NONE = -1
|
|
192
|
+
FSTRING_CONV_STR = 115 # !s
|
|
193
|
+
FSTRING_CONV_REPR = 114 # !r
|
|
194
|
+
FSTRING_CONV_ASCII = 97 # !a
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dataclass
|
|
198
|
+
class TpyFStringValue:
|
|
199
|
+
"""Formatted expression inside an f-string: {expr:spec}."""
|
|
200
|
+
expr: TpyExpr
|
|
201
|
+
conversion: int = FSTRING_CONV_NONE
|
|
202
|
+
format_spec: str | None = None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@dataclass
|
|
206
|
+
class TpyFString(TpyExpr):
|
|
207
|
+
"""F-string: f"text {expr:spec} more text"."""
|
|
208
|
+
parts: list[str | TpyFStringValue]
|
|
209
|
+
|
|
210
|
+
def children(self) -> list[TpyExpr]:
|
|
211
|
+
return [p.expr for p in self.parts if isinstance(p, TpyFStringValue)]
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass
|
|
215
|
+
class TpyBoolLiteral(TpyExpr):
|
|
216
|
+
"""Boolean literal."""
|
|
217
|
+
value: bool
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@dataclass
|
|
221
|
+
class TpyNoneLiteral(TpyExpr):
|
|
222
|
+
"""None literal."""
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class TpyName(TpyExpr):
|
|
228
|
+
"""Variable reference."""
|
|
229
|
+
name: str
|
|
230
|
+
is_function_ref: bool = False # Set by sema: name resolves to a function used as a value
|
|
231
|
+
function_ref_info: 'FunctionInfo | None' = None # Set by sema: resolved function for codegen
|
|
232
|
+
function_ref_type_args: 'tuple[TpyType, ...] | None' = None # Set by sema: inferred type args for generic function refs
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@dataclass
|
|
236
|
+
class TpyBinOp(TpyExpr):
|
|
237
|
+
"""Binary operation."""
|
|
238
|
+
left: TpyExpr
|
|
239
|
+
op: str # '+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>='
|
|
240
|
+
right: TpyExpr
|
|
241
|
+
resolved_binop: 'ResolvedBinop | None' = None # Set by sema for builtin ops
|
|
242
|
+
resolved_contains: 'FunctionInfo | None' = None # Set by sema for 'in'/'not in' with __contains__
|
|
243
|
+
typed_dict_in_field: str | None = None # Set by sema: "key" in TypedDict -> field presence check
|
|
244
|
+
typed_dict_in_always_true: bool = False # Set by sema: total=True field, fold to True
|
|
245
|
+
optional_safe_eq: bool = False # Set by sema: ==/!= with Optional value-type operand(s)
|
|
246
|
+
int_enum_coercion: 'NominalType | None' = None # Set by sema: IntEnum arithmetic coerced to underlying type
|
|
247
|
+
divisor_non_zero: bool = False # Set by sema: divisor provably non-zero, skip div-zero check
|
|
248
|
+
|
|
249
|
+
def children(self) -> list[TpyExpr]:
|
|
250
|
+
return [self.left, self.right]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@dataclass
|
|
254
|
+
class TpyChainedCompare(TpyExpr):
|
|
255
|
+
"""Chained comparison: a < b < c desugars to (a < b) and (b < c)."""
|
|
256
|
+
left: TpyExpr
|
|
257
|
+
ops: list[str]
|
|
258
|
+
comparators: list[TpyExpr]
|
|
259
|
+
# Set by sema: synthetic TpyBinOp for each comparison pair
|
|
260
|
+
pairs: list['TpyBinOp'] | None = None
|
|
261
|
+
|
|
262
|
+
def children(self) -> list[TpyExpr]:
|
|
263
|
+
return [self.left] + self.comparators
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@dataclass
|
|
267
|
+
class TpyUnaryOp(TpyExpr):
|
|
268
|
+
"""Unary operation."""
|
|
269
|
+
op: str # '-', 'not'
|
|
270
|
+
operand: TpyExpr
|
|
271
|
+
resolved_unaryop: 'ResolvedUnaryop | None' = None # Set by sema for builtin ops
|
|
272
|
+
|
|
273
|
+
def children(self) -> list[TpyExpr]:
|
|
274
|
+
return [self.operand]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@dataclass
|
|
278
|
+
class TpyTypeParamConstruct(TpyExpr):
|
|
279
|
+
"""Default-construction of a type parameter: T() in a default value."""
|
|
280
|
+
param_name: str
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass
|
|
284
|
+
class TpyVarargPack(TpyExpr):
|
|
285
|
+
"""Pack of variadic arguments at a call site (created by sema, not parser).
|
|
286
|
+
|
|
287
|
+
Represents the trailing args that get packed into a Span[readonly[T]] for
|
|
288
|
+
a *args: T parameter.
|
|
289
|
+
"""
|
|
290
|
+
args: list[TpyExpr]
|
|
291
|
+
element_type: TpyType
|
|
292
|
+
|
|
293
|
+
def children(self) -> list[TpyExpr]:
|
|
294
|
+
return list(self.args)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclass
|
|
298
|
+
class TpyStarUnpack(TpyExpr):
|
|
299
|
+
"""Star unpacking at a call site: f(*expr). Created by parser for *expr in calls."""
|
|
300
|
+
expr: TpyExpr
|
|
301
|
+
|
|
302
|
+
def children(self) -> list[TpyExpr]:
|
|
303
|
+
return [self.expr]
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@dataclass
|
|
307
|
+
class TpyCall(TpyExpr):
|
|
308
|
+
"""Function or constructor call.
|
|
309
|
+
|
|
310
|
+
For generic function calls like first[Int32](items):
|
|
311
|
+
- type_args stores the explicit type arguments (e.g., (Int32,))
|
|
312
|
+
- inferred_type_args is set by sema for codegen (resolved from inference or explicit)
|
|
313
|
+
"""
|
|
314
|
+
func: TpyExpr # TpyName for simple calls; arbitrary TpyExpr for expression callees
|
|
315
|
+
args: list[TpyExpr]
|
|
316
|
+
# Between parse and the post-parse resolve_refs pass,
|
|
317
|
+
# call_type and type_args may hold TypeRefNode in place of TpyType.
|
|
318
|
+
# All readers post-pre-pass see TpyType.
|
|
319
|
+
call_type: 'TpyType | TypeRefNode | None' = None # For generic instantiation like MyContainer[T, N]()
|
|
320
|
+
type_args: 'tuple[TpyType | TypeRefNode | None, ...]' = () # Explicit type args for generic function calls: func[T](args)
|
|
321
|
+
inferred_type_args: tuple[TpyType, ...] | None = None # Set by sema for generic function calls
|
|
322
|
+
type_args_parse_error: str | None = None # Set if subscript had args that couldn't be parsed as types
|
|
323
|
+
subscript_callee: 'TpyExpr | None' = None # Set by parser: fns[0](args) -> stores TpySubscript(fns, 0) for sema fallback
|
|
324
|
+
kwargs: dict[str, TpyExpr] = field(default_factory=dict) # Keyword arguments (limited support)
|
|
325
|
+
double_star_unpack: 'TpyExpr | None' = None # **expr unpacking at call site
|
|
326
|
+
kwarg_td_call: 'TpyExpr | None' = None # Set by sema: synthetic TypedDict construction for **kwargs
|
|
327
|
+
resolved_import: tuple[str, str] | None = None # Set by parser: (module, name) for resolved imports
|
|
328
|
+
resolved_function_info: FunctionInfo | None = None # Set by sema for resolved function overloads
|
|
329
|
+
enum_from_value: NominalType | None = None # Set by sema for enum value lookup: Color(0)
|
|
330
|
+
isinstance_var: str | None = None # Set by sema: variable name being isinstance-checked
|
|
331
|
+
isinstance_type: TpyType | None = None # Set by sema: resolved type being checked for
|
|
332
|
+
isinstance_is_protocol: bool = False # Set by sema: protocol isinstance (if constexpr)
|
|
333
|
+
# Set by sema (>0) when the isinstance source is an owning wrapper
|
|
334
|
+
# (Box[Pet]/Rc[Pet]): the dispatch target is the polymorphic payload
|
|
335
|
+
# reached through `depth` reference-returning __deref__ steps, not the
|
|
336
|
+
# variable itself. Drives deref-view narrowing (the wrapper's own type is
|
|
337
|
+
# never narrowed) and the deref-payload-pointer cast in codegen.
|
|
338
|
+
isinstance_deref_depth: int = 0
|
|
339
|
+
cast_target_type: TpyType | None = None # Set by sema for typing.cast(T, x): the resolved target type
|
|
340
|
+
cast_source_is_any: bool = False # Set by sema for typing.cast: True iff source's static type is Any
|
|
341
|
+
macro_expansion: 'TpyExpr | None' = None # Set by sema: replacement expr from @call_macro
|
|
342
|
+
dunder_call: 'TpyMethodCall | None' = None # Set by sema: obj(args) -> obj.__call__(args)
|
|
343
|
+
# D16 v1.5: try/catch lambda forms. Inner __getattr__ call is stored here;
|
|
344
|
+
# codegen wraps it in a lambda that catches AttributeError.
|
|
345
|
+
dyn_hasattr_call: 'TpyMethodCall | None' = None # hasattr(obj, "name") -> bool
|
|
346
|
+
dyn_getattr_default_call: 'TpyMethodCall | None' = None # getattr(obj, "name", default) -> T
|
|
347
|
+
# Method-local type params marked representational (`Ptr[U] -> Ptr[T]` in
|
|
348
|
+
# the callee body) whose substituted bound is @dynamic AND whose inferred
|
|
349
|
+
# type arg is a structural conformer needing Adapter wrap. Set by sema
|
|
350
|
+
# during call analysis; consulted by codegen to substitute the C++ template
|
|
351
|
+
# arg `U -> Adapter<T_sub, U_sub>`. None when the call doesn't need any
|
|
352
|
+
# substitution. Empty frozenset is never written -- absence is None.
|
|
353
|
+
representational_subst_params: frozenset[str] | None = None
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def func_name(self) -> str:
|
|
357
|
+
"""Name of the callee. Only valid when func is a TpyName (simple call)."""
|
|
358
|
+
assert isinstance(self.func, TpyName), f"func_name on non-Name callee: {type(self.func).__name__}"
|
|
359
|
+
return self.func.name
|
|
360
|
+
|
|
361
|
+
@property
|
|
362
|
+
def maybe_func_name(self) -> str | None:
|
|
363
|
+
"""Name of the callee, or None if the callee isn't a TpyName
|
|
364
|
+
(subscript-callee `arr[0]()`, expression callee `(get())()` etc.).
|
|
365
|
+
Use when probing whether a TpyCall targets a known free function
|
|
366
|
+
without first narrowing the callee shape.
|
|
367
|
+
"""
|
|
368
|
+
return self.func.name if isinstance(self.func, TpyName) else None
|
|
369
|
+
|
|
370
|
+
def children(self) -> list[TpyExpr]:
|
|
371
|
+
if self.macro_expansion is not None:
|
|
372
|
+
return [self.macro_expansion]
|
|
373
|
+
extra = [self.subscript_callee] if self.subscript_callee is not None else []
|
|
374
|
+
return [self.func] + list(self.args) + list(self.kwargs.values()) + extra
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
@dataclass
|
|
378
|
+
class TpyMethodCall(TpyExpr):
|
|
379
|
+
"""Method call on an object."""
|
|
380
|
+
obj: TpyExpr
|
|
381
|
+
method: str
|
|
382
|
+
args: list[TpyExpr]
|
|
383
|
+
kwargs: dict[str, TpyExpr] = field(default_factory=dict)
|
|
384
|
+
double_star_unpack: 'TpyExpr | None' = None # **expr unpacking at call site
|
|
385
|
+
resolved_import: tuple[str, str] | None = None # Set by parser: (module, name) for resolved imports
|
|
386
|
+
# May hold TypeRefNode pre-sema-pre-pass.
|
|
387
|
+
type_args: 'tuple[TpyType | TypeRefNode | None, ...]' = () # Explicit type args for module.func[T](args) syntax
|
|
388
|
+
type_args_parse_error: str | None = None # Set if subscript had args that couldn't be parsed as types
|
|
389
|
+
is_static_call: bool = False # Set by sema for ClassName.staticmethod() calls
|
|
390
|
+
super_parent_type: Optional[TpyType] = None # Set by sema for super().method() calls
|
|
391
|
+
unbound_self_parent_type: Optional[TpyType] = None # Set by sema for BaseN.method(self, ...) calls on an ancestor
|
|
392
|
+
user_module_call: Optional[str] = None # Set by sema for module.func() calls to user modules
|
|
393
|
+
builtin_module_call: Optional[str] = None # Set by sema for builtin module.func() calls (canonical module name)
|
|
394
|
+
needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
|
|
395
|
+
resolved_function_info: FunctionInfo | None = None # Set by sema for resolved method overloads
|
|
396
|
+
inferred_type_args: tuple[TpyType, ...] | None = None # Set by sema for generic builtin module calls
|
|
397
|
+
deref_depth: int = 0 # Set by sema: number of __deref__ steps applied before method resolution
|
|
398
|
+
# Set by sema to the narrowed subclass when this method resolves through a
|
|
399
|
+
# deref-view narrowing (`if isinstance(rc, Dog): rc.bark()`); codegen casts
|
|
400
|
+
# the deref payload pointer to it instead of emitting a plain __deref__ call.
|
|
401
|
+
deref_narrowed_to: TpyType | None = None
|
|
402
|
+
ptr_non_null: bool = False # Set by sema: receiver is a provably non-null Ptr (or Ptr[readonly[T]])
|
|
403
|
+
is_callable_field: bool = False # Set by sema: method name is a Callable-typed field
|
|
404
|
+
macro_expansion: 'TpyExpr | None' = None # Set by sema: replacement expr from @call_macro
|
|
405
|
+
typed_dict_get_field: str | None = None # Set by sema: td.get("key") -> field access
|
|
406
|
+
typed_dict_get_optional: bool = False # Set by sema: total=False field, absent by default
|
|
407
|
+
fstr_expansion: 'TpyExpr | None' = None # Set by sema: inlined FStr method body
|
|
408
|
+
is_nested_constructor: bool = False # Set by sema: Outer.Inner() nested record constructor
|
|
409
|
+
is_nested_enum_constructor: bool = False # Set by sema: Outer.Kind(v) nested enum from_value
|
|
410
|
+
nested_type_name: str | None = None # Set by sema: dotted name for nested type calls
|
|
411
|
+
# See TpyCall.representational_subst_params for the contract.
|
|
412
|
+
representational_subst_params: frozenset[str] | None = None
|
|
413
|
+
|
|
414
|
+
def children(self) -> list[TpyExpr]:
|
|
415
|
+
if self.fstr_expansion is not None:
|
|
416
|
+
return [self.fstr_expansion]
|
|
417
|
+
if self.macro_expansion is not None:
|
|
418
|
+
return [self.macro_expansion]
|
|
419
|
+
return [self.obj] + list(self.args) + list(self.kwargs.values())
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
@dataclass
|
|
423
|
+
class TpyFieldAccess(TpyExpr):
|
|
424
|
+
"""Field access on a value or pointer."""
|
|
425
|
+
obj: TpyExpr
|
|
426
|
+
field: str
|
|
427
|
+
needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
|
|
428
|
+
deref_depth: int = 0 # Set by sema: number of __deref__ steps applied before field lookup
|
|
429
|
+
# Set by sema to the narrowed subclass when this field access resolves
|
|
430
|
+
# through a deref-view narrowing; codegen casts the deref payload pointer.
|
|
431
|
+
deref_narrowed_to: TpyType | None = None
|
|
432
|
+
ptr_non_null: bool = False # Set by sema: receiver is a provably non-null Ptr (or Ptr[readonly[T]])
|
|
433
|
+
is_property_access: bool = False # Set by sema: this is a property getter
|
|
434
|
+
property_setter: bool = False # Set by sema: assignment target is a property setter
|
|
435
|
+
property_getter_call: 'TpyMethodCall | None' = None # Set by sema: getter method call for codegen
|
|
436
|
+
property_setter_call: 'TpyMethodCall | None' = None # Set by sema: setter method call for codegen
|
|
437
|
+
dyn_getattr_call: 'TpyMethodCall | None' = None # Set by sema: __getattr__ fallback method call (D16)
|
|
438
|
+
dyn_setattr_call: 'TpyMethodCall | None' = None # Set by sema: __setattr__ fallback method call (D16)
|
|
439
|
+
dyn_delattr_call: 'TpyMethodCall | None' = None # Set by sema: __delattr__ fallback method call (D16)
|
|
440
|
+
unbound_self_parent_type: Optional[TpyType] = None # Set by sema for BaseN.field access on an ancestor subobject
|
|
441
|
+
class_constant_owner: Optional['RecordInfo'] = None # Set by sema: RecordInfo for ClassName.X class-constant access; codegen emits <cpp_qname>::<member>
|
|
442
|
+
module_var_access: Optional[tuple[str, str]] = None # Set by sema for `pkg.sub.X` variable access on a dotted module: (module_qname, var_name)
|
|
443
|
+
|
|
444
|
+
def children(self) -> list[TpyExpr]:
|
|
445
|
+
return [self.obj]
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
@dataclass
|
|
449
|
+
class TpyArrayLiteral(TpyExpr):
|
|
450
|
+
"""Array literal: [expr, expr, ...]"""
|
|
451
|
+
elements: list[TpyExpr]
|
|
452
|
+
|
|
453
|
+
def children(self) -> list[TpyExpr]:
|
|
454
|
+
return list(self.elements)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class TupleElemCapture(IntEnum):
|
|
458
|
+
"""How a tuple literal element captures its value."""
|
|
459
|
+
VALUE = 0 # T -- owned copy
|
|
460
|
+
REF = 1 # T& -- mutable reference
|
|
461
|
+
CONST_REF = 2 # const T& -- immutable reference
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@dataclass
|
|
465
|
+
class TpyTupleLiteral(TpyExpr):
|
|
466
|
+
"""Tuple literal: (expr, expr, ...)"""
|
|
467
|
+
elements: list[TpyExpr]
|
|
468
|
+
# Set by sema: per-element capture mode
|
|
469
|
+
elem_capture: list[TupleElemCapture] = field(default_factory=list)
|
|
470
|
+
|
|
471
|
+
def children(self) -> list[TpyExpr]:
|
|
472
|
+
return list(self.elements)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@dataclass
|
|
476
|
+
class TpyListRepeat(TpyExpr):
|
|
477
|
+
"""List repetition: [elements...] * count -> sequence repeated count times"""
|
|
478
|
+
elements: list[TpyExpr]
|
|
479
|
+
count: TpyExpr
|
|
480
|
+
|
|
481
|
+
def children(self) -> list[TpyExpr]:
|
|
482
|
+
return list(self.elements) + [self.count]
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@dataclass
|
|
486
|
+
class TpyComprehensionGenerator:
|
|
487
|
+
"""Single generator clause: for var in iterable [if cond]*"""
|
|
488
|
+
var: str
|
|
489
|
+
iterable: TpyExpr
|
|
490
|
+
conditions: list[TpyExpr]
|
|
491
|
+
unpack_vars: list[str | None] | None = None # Phase 3: tuple unpacking
|
|
492
|
+
const_loop_var: bool = False
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
@dataclass
|
|
496
|
+
class TpyListComprehension(TpyExpr):
|
|
497
|
+
"""List comprehension: [expr for var in iterable if cond]"""
|
|
498
|
+
element_expr: TpyExpr
|
|
499
|
+
generator: TpyComprehensionGenerator
|
|
500
|
+
result_elem_type: 'TpyType | None' = None # set by sema
|
|
501
|
+
|
|
502
|
+
def children(self) -> list[TpyExpr]:
|
|
503
|
+
return [self.element_expr, self.generator.iterable] + self.generator.conditions
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
@dataclass
|
|
507
|
+
class TpyDictComprehension(TpyExpr):
|
|
508
|
+
"""Dict comprehension: {key: value for var in iterable if cond}"""
|
|
509
|
+
key_expr: TpyExpr
|
|
510
|
+
value_expr: TpyExpr
|
|
511
|
+
generator: TpyComprehensionGenerator
|
|
512
|
+
result_key_type: 'TpyType | None' = None # set by sema
|
|
513
|
+
result_value_type: 'TpyType | None' = None # set by sema
|
|
514
|
+
|
|
515
|
+
def children(self) -> list[TpyExpr]:
|
|
516
|
+
return [self.key_expr, self.value_expr, self.generator.iterable] + self.generator.conditions
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@dataclass
|
|
520
|
+
class TpyDictLiteral(TpyExpr):
|
|
521
|
+
"""Dict literal: {key: value, key: value, ...}"""
|
|
522
|
+
keys: list[TpyExpr]
|
|
523
|
+
values: list[TpyExpr]
|
|
524
|
+
|
|
525
|
+
def children(self) -> list[TpyExpr]:
|
|
526
|
+
return list(self.keys) + list(self.values)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@dataclass
|
|
530
|
+
class TpySetComprehension(TpyExpr):
|
|
531
|
+
"""Set comprehension: {expr for var in iterable if cond}"""
|
|
532
|
+
element_expr: TpyExpr
|
|
533
|
+
generator: TpyComprehensionGenerator
|
|
534
|
+
result_elem_type: 'TpyType | None' = None # set by sema
|
|
535
|
+
|
|
536
|
+
def children(self) -> list[TpyExpr]:
|
|
537
|
+
return [self.element_expr, self.generator.iterable] + self.generator.conditions
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
@dataclass
|
|
541
|
+
class TpyGeneratorExpression(TpyExpr):
|
|
542
|
+
"""Generator expression: (expr for var in iterable if cond)"""
|
|
543
|
+
element_expr: TpyExpr
|
|
544
|
+
generator: TpyComprehensionGenerator
|
|
545
|
+
result_elem_type: 'TpyType | None' = None # set by sema
|
|
546
|
+
|
|
547
|
+
def children(self) -> list[TpyExpr]:
|
|
548
|
+
return [self.element_expr, self.generator.iterable] + self.generator.conditions
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@dataclass
|
|
552
|
+
class TpyLambda(TpyExpr):
|
|
553
|
+
"""Lambda expression: lambda x, y: x + y.
|
|
554
|
+
|
|
555
|
+
Parameter types are inferred from context (Fn type hint) during sema.
|
|
556
|
+
"""
|
|
557
|
+
param_names: list[str]
|
|
558
|
+
body: TpyExpr
|
|
559
|
+
inferred_param_types: list['TpyType'] = field(default_factory=list)
|
|
560
|
+
inferred_return_type: 'TpyType | None' = None
|
|
561
|
+
captured_names: list[str] = field(default_factory=list)
|
|
562
|
+
captures_by_value: bool = False # True for Callable context (captures escape)
|
|
563
|
+
readonly_params: bool = False # True when params should be const (key functions)
|
|
564
|
+
|
|
565
|
+
def children(self) -> list[TpyExpr]:
|
|
566
|
+
# Lambda creates its own scope; outer walks should not recurse into
|
|
567
|
+
# the body. Lambda analysis in sema recurses into body explicitly.
|
|
568
|
+
return []
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@dataclass
|
|
572
|
+
class TpySetLiteral(TpyExpr):
|
|
573
|
+
"""Set literal: {value, value, ...}"""
|
|
574
|
+
elements: list[TpyExpr]
|
|
575
|
+
|
|
576
|
+
def children(self) -> list[TpyExpr]:
|
|
577
|
+
return list(self.elements)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
@dataclass
|
|
581
|
+
class TpySlice(TpyExpr):
|
|
582
|
+
"""Slice expression: lower:upper or lower:upper:step."""
|
|
583
|
+
lower: TpyExpr | None = None
|
|
584
|
+
upper: TpyExpr | None = None
|
|
585
|
+
step: TpyExpr | None = None # reserved for future step support
|
|
586
|
+
|
|
587
|
+
def children(self) -> list[TpyExpr]:
|
|
588
|
+
return [x for x in (self.lower, self.upper, self.step) if x is not None]
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@dataclass
|
|
592
|
+
class TpySubscript(TpyExpr):
|
|
593
|
+
"""Subscript indexing: obj[index]"""
|
|
594
|
+
obj: TpyExpr
|
|
595
|
+
index: TpyExpr # TpySlice for slicing, other TpyExpr for single-index
|
|
596
|
+
needs_optional_runtime_check: bool = False # Set by sema for unproven Optional access
|
|
597
|
+
enum_from_name: 'NominalType | None' = None # Set by sema for Color["Red"] name lookup
|
|
598
|
+
bounds_safe: bool = False # Set by sema: index provably in [0, len(obj)), skip bounds check
|
|
599
|
+
is_stepped_slice: bool = False # Set by sema: slice has step (a[::2])
|
|
600
|
+
slice_function_info: 'FunctionInfo | None' = None # Set by sema: resolved __getitem__ for slice
|
|
601
|
+
typed_dict_field: str | None = None # Set by sema: d["key"] on TypedDict -> field access
|
|
602
|
+
typed_dict_optional: bool = False # Set by sema: total=False field, needs runtime check
|
|
603
|
+
|
|
604
|
+
def children(self) -> list[TpyExpr]:
|
|
605
|
+
return [self.obj, self.index]
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
@dataclass
|
|
609
|
+
class TpyCoerce(TpyExpr):
|
|
610
|
+
"""Expression with an explicit coercion attached by semantic analysis."""
|
|
611
|
+
expr: TpyExpr
|
|
612
|
+
actual_type: TpyType
|
|
613
|
+
expected_type: TpyType
|
|
614
|
+
coercion: "Coercion"
|
|
615
|
+
context_kind: str
|
|
616
|
+
context_msg: str
|
|
617
|
+
runtime_bigint: bool = False
|
|
618
|
+
|
|
619
|
+
def children(self) -> list[TpyExpr]:
|
|
620
|
+
return [self.expr]
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
@dataclass
|
|
624
|
+
class TpyIfExpr(TpyExpr):
|
|
625
|
+
"""Ternary conditional expression: then_expr if condition else else_expr."""
|
|
626
|
+
condition: TpyExpr
|
|
627
|
+
then_expr: TpyExpr
|
|
628
|
+
else_expr: TpyExpr
|
|
629
|
+
|
|
630
|
+
def children(self) -> list[TpyExpr]:
|
|
631
|
+
return [self.condition, self.then_expr, self.else_expr]
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@dataclass
|
|
635
|
+
class TpyNamedExpr(TpyExpr):
|
|
636
|
+
"""Walrus operator: (x := expr)."""
|
|
637
|
+
target: str
|
|
638
|
+
value: TpyExpr
|
|
639
|
+
|
|
640
|
+
def children(self) -> list[TpyExpr]:
|
|
641
|
+
return [self.value]
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@dataclass
|
|
645
|
+
class TpyAwait(TpyExpr):
|
|
646
|
+
"""`await x` expression. Lowered by PR 3 (resumable-frame codegen)
|
|
647
|
+
to a poll-and-park sequence on the operand's Awaitable[T] conformance.
|
|
648
|
+
Sema rejects awaits outside an async def body.
|
|
649
|
+
|
|
650
|
+
Sema attaches one of two resolution shapes:
|
|
651
|
+
- `awaited_async_func_name`: inline mode -- the operand is a direct
|
|
652
|
+
call to a known async def. Codegen emits the sub-coroutine struct
|
|
653
|
+
as a frame field via `std::optional<__<name>Coro>` and constructs
|
|
654
|
+
it in place from the call's args.
|
|
655
|
+
For async methods, `awaited_method_owner_type` carries the
|
|
656
|
+
receiver's NominalType (including its type args) so the sub-coro
|
|
657
|
+
struct can be uniquely named (`__coro_<Record>_<method>`) and
|
|
658
|
+
qualified with the receiver's class-level template args.
|
|
659
|
+
- `awaited_task_inner`: erased mode -- the operand has type
|
|
660
|
+
`Task[T]`. Codegen emits the sub-future field as
|
|
661
|
+
`std::optional<::tpy::Task<T>>` and moves the operand value in.
|
|
662
|
+
Exactly one is set on a successfully-analyzed TpyAwait.
|
|
663
|
+
|
|
664
|
+
For generic async defs (`async def f[T](...) -> T`), codegen reads
|
|
665
|
+
the inferred type args off the operand call node directly
|
|
666
|
+
(`value.inferred_type_args`) to qualify the sub-coro struct name as
|
|
667
|
+
`__coro_<name><A, B>`. The await expression's substituted result
|
|
668
|
+
type is recorded via the standard expression-type table.
|
|
669
|
+
|
|
670
|
+
`suspension_index` is the ordinal (in source order) within the
|
|
671
|
+
enclosing async def, used to name __sub_<i> fields and
|
|
672
|
+
S_AFTER_AWAIT_<i> states.
|
|
673
|
+
"""
|
|
674
|
+
value: TpyExpr
|
|
675
|
+
awaited_async_func_name: str | None = field(default=None, kw_only=True)
|
|
676
|
+
awaited_method_owner_type: 'NominalType | None' = field(default=None, kw_only=True)
|
|
677
|
+
awaited_task_inner: 'TpyType | None' = field(default=None, kw_only=True)
|
|
678
|
+
suspension_index: int | None = field(default=None, kw_only=True)
|
|
679
|
+
|
|
680
|
+
def children(self) -> list[TpyExpr]:
|
|
681
|
+
return [self.value]
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
@dataclass
|
|
685
|
+
class TpyStmt:
|
|
686
|
+
"""Base class for statements."""
|
|
687
|
+
loc: SourceLocation | None = field(default=None, kw_only=True)
|
|
688
|
+
|
|
689
|
+
def exprs(self) -> list[TpyExpr]:
|
|
690
|
+
"""Return direct child expressions for generic tree walking."""
|
|
691
|
+
return []
|
|
692
|
+
|
|
693
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
694
|
+
"""Return sub-statement bodies for recursive walking."""
|
|
695
|
+
return []
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
class VarLinkage(Enum):
|
|
699
|
+
"""Linkage mode for global variable imports."""
|
|
700
|
+
DEFAULT = "default"
|
|
701
|
+
NATIVE = "native" # C++ global import
|
|
702
|
+
NATIVE_C = "native_c" # C global import (extern "C")
|
|
703
|
+
NATIVE_C_ARRAY = "native_c_array" # C array global (extern "C" T name[])
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
@dataclass
|
|
707
|
+
class TpyVarDecl(TpyStmt):
|
|
708
|
+
"""Variable declaration with optional initializer.
|
|
709
|
+
|
|
710
|
+
`type` may temporarily hold a TypeRefNode when emitted by the parser at
|
|
711
|
+
annotation sites; the sema writer (`_analyze_var_decl`) calls
|
|
712
|
+
`TypeOperations.resolve_type_ref` to produce a TpyType and writes it
|
|
713
|
+
back in place. All downstream sema/codegen reads see only TpyType.
|
|
714
|
+
"""
|
|
715
|
+
name: str
|
|
716
|
+
type: Optional[TpyType | TypeRefNode]
|
|
717
|
+
init: Optional[TpyExpr]
|
|
718
|
+
linkage: VarLinkage = VarLinkage.DEFAULT
|
|
719
|
+
native_name: str | None = None
|
|
720
|
+
# TODO: is_final (and linkage) could be generalized into a modifiers set
|
|
721
|
+
# (e.g. modifiers: set[str]) to avoid per-feature boolean fields
|
|
722
|
+
is_final: bool = False # Set by sema for Final[T] constant globals
|
|
723
|
+
# Set by sema: union assignment narrowing facts for codegen
|
|
724
|
+
then_type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
725
|
+
|
|
726
|
+
def exprs(self) -> list[TpyExpr]:
|
|
727
|
+
return [self.init] if self.init else []
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
@dataclass
|
|
731
|
+
class TpyTupleUnpack(TpyStmt):
|
|
732
|
+
"""Tuple unpacking: a, b = expr. None in targets means discard (_)."""
|
|
733
|
+
targets: list[str | None]
|
|
734
|
+
value: TpyExpr
|
|
735
|
+
# Set by sema:
|
|
736
|
+
target_types: list[TpyType] = field(default_factory=list)
|
|
737
|
+
is_new: list[bool] = field(default_factory=list)
|
|
738
|
+
is_owned: list[bool] = field(default_factory=list)
|
|
739
|
+
is_ref: list[bool] = field(default_factory=list)
|
|
740
|
+
is_const_ref: list[bool] = field(default_factory=list)
|
|
741
|
+
|
|
742
|
+
def exprs(self) -> list[TpyExpr]:
|
|
743
|
+
return [self.value]
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
@dataclass
|
|
747
|
+
class TpyAssign(TpyStmt):
|
|
748
|
+
"""Assignment to variable or field."""
|
|
749
|
+
target: TpyExpr
|
|
750
|
+
value: TpyExpr
|
|
751
|
+
|
|
752
|
+
def exprs(self) -> list[TpyExpr]:
|
|
753
|
+
return [self.target, self.value]
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
@dataclass
|
|
757
|
+
class TpyAugAssign(TpyStmt):
|
|
758
|
+
"""Augmented assignment (+=, -=, etc.)"""
|
|
759
|
+
target: TpyExpr
|
|
760
|
+
op: str
|
|
761
|
+
value: TpyExpr
|
|
762
|
+
resolved_binop: 'ResolvedBinop | None' = None # Set by sema for builtin ops
|
|
763
|
+
resolved_inplace: 'ResolvedBinop | None' = None # Set by sema for in-place ops (__iadd__, __ior__, etc.)
|
|
764
|
+
|
|
765
|
+
def exprs(self) -> list[TpyExpr]:
|
|
766
|
+
return [self.target, self.value]
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
@dataclass
|
|
770
|
+
class TpyDelItem(TpyStmt):
|
|
771
|
+
"""Delete statement: del obj[key], obj2[key2], ..."""
|
|
772
|
+
targets: list[TpySubscript]
|
|
773
|
+
|
|
774
|
+
def exprs(self) -> list[TpyExpr]:
|
|
775
|
+
return list(self.targets)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
@dataclass
|
|
779
|
+
class TpyDelVar(TpyStmt):
|
|
780
|
+
"""Delete statement for local variables: del x, y, ..."""
|
|
781
|
+
names: list[str]
|
|
782
|
+
|
|
783
|
+
def exprs(self) -> list[TpyExpr]:
|
|
784
|
+
return []
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
@dataclass
|
|
788
|
+
class TpyDelAttr(TpyStmt):
|
|
789
|
+
"""Delete statement for attributes: del obj.foo, obj2.bar, ... (D16 Phase 3)"""
|
|
790
|
+
targets: list[TpyFieldAccess]
|
|
791
|
+
|
|
792
|
+
def exprs(self) -> list[TpyExpr]:
|
|
793
|
+
return list(self.targets)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
@dataclass
|
|
797
|
+
class TpyExprStmt(TpyStmt):
|
|
798
|
+
"""Expression statement (e.g., function call)."""
|
|
799
|
+
expr: TpyExpr
|
|
800
|
+
|
|
801
|
+
def exprs(self) -> list[TpyExpr]:
|
|
802
|
+
return [self.expr]
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
@dataclass
|
|
806
|
+
class TpyReturn(TpyStmt):
|
|
807
|
+
"""Return statement."""
|
|
808
|
+
value: Optional[TpyExpr]
|
|
809
|
+
# Set by sema: the analyzed type of the return expression (before coercion)
|
|
810
|
+
value_type: Optional['TpyType'] = None
|
|
811
|
+
|
|
812
|
+
def exprs(self) -> list[TpyExpr]:
|
|
813
|
+
return [self.value] if self.value else []
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
@dataclass
|
|
817
|
+
class TpyYield(TpyStmt):
|
|
818
|
+
"""Yield statement in a generator function."""
|
|
819
|
+
value: TpyExpr
|
|
820
|
+
|
|
821
|
+
def exprs(self) -> list[TpyExpr]:
|
|
822
|
+
return [self.value]
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
@dataclass
|
|
826
|
+
class TpyAssert(TpyStmt):
|
|
827
|
+
"""Assert statement."""
|
|
828
|
+
condition: TpyExpr
|
|
829
|
+
message: TpyExpr | None = None
|
|
830
|
+
# Set by sema: isinstance union narrowing facts that hold after a passing assert
|
|
831
|
+
then_type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
832
|
+
|
|
833
|
+
def exprs(self) -> list[TpyExpr]:
|
|
834
|
+
result = [self.condition]
|
|
835
|
+
if self.message:
|
|
836
|
+
result.append(self.message)
|
|
837
|
+
return result
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
@dataclass
|
|
841
|
+
class TpyIf(TpyStmt):
|
|
842
|
+
"""If statement."""
|
|
843
|
+
condition: TpyExpr
|
|
844
|
+
then_body: list[TpyStmt]
|
|
845
|
+
else_body: list[TpyStmt]
|
|
846
|
+
# Set by sema: isinstance union narrowing facts for codegen
|
|
847
|
+
then_type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
848
|
+
else_type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
849
|
+
|
|
850
|
+
def exprs(self) -> list[TpyExpr]:
|
|
851
|
+
return [self.condition]
|
|
852
|
+
|
|
853
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
854
|
+
return [self.then_body, self.else_body]
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
@dataclass
|
|
858
|
+
class TpyWhile(TpyStmt):
|
|
859
|
+
"""While loop."""
|
|
860
|
+
condition: TpyExpr
|
|
861
|
+
body: list[TpyStmt]
|
|
862
|
+
orelse: list[TpyStmt] = field(default_factory=list)
|
|
863
|
+
# Set by sema: isinstance union narrowing facts for codegen
|
|
864
|
+
then_type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
865
|
+
|
|
866
|
+
def exprs(self) -> list[TpyExpr]:
|
|
867
|
+
return [self.condition]
|
|
868
|
+
|
|
869
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
870
|
+
return [self.body, self.orelse]
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
@dataclass
|
|
874
|
+
class TpyForEach(TpyStmt):
|
|
875
|
+
"""For-each loop over a collection."""
|
|
876
|
+
var: str
|
|
877
|
+
iterable: TpyExpr
|
|
878
|
+
body: list[TpyStmt]
|
|
879
|
+
orelse: list[TpyStmt] = field(default_factory=list)
|
|
880
|
+
enum_iterable: 'NominalType | None' = None # set by sema when iterating over enum type
|
|
881
|
+
elem_type: 'TpyType | None' = None # set by sema: resolved element type for codegen
|
|
882
|
+
is_tuple_unpack: bool = False # set by parser: synthetic loop var for tuple destructuring
|
|
883
|
+
const_loop_var: bool = False # set by sema: loop var is never mutated, safe for const auto&
|
|
884
|
+
hoist_loop_var: bool = False # set by sema: loop var used after loop, needs pre-declaration
|
|
885
|
+
consuming_iter_fi: 'FunctionInfo | None' = None # set by sema: consuming __iter__ overload at last use
|
|
886
|
+
is_async: bool = False # Set by parser: `async for` (lowers to __aiter__/await __anext__ inside async def)
|
|
887
|
+
async_aiter_type: 'NominalType | None' = None # set by sema for async-for: resolved aiter record (return of __aiter__())
|
|
888
|
+
|
|
889
|
+
def exprs(self) -> list[TpyExpr]:
|
|
890
|
+
return [self.iterable]
|
|
891
|
+
|
|
892
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
893
|
+
return [self.body, self.orelse]
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
@dataclass
|
|
897
|
+
class TpyBreak(TpyStmt):
|
|
898
|
+
"""Break statement."""
|
|
899
|
+
pass
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
@dataclass
|
|
903
|
+
class TpyContinue(TpyStmt):
|
|
904
|
+
"""Continue statement."""
|
|
905
|
+
pass
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
@dataclass
|
|
909
|
+
class TpyPassStmt(TpyStmt):
|
|
910
|
+
"""Pass statement (no-op)."""
|
|
911
|
+
pass
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
@dataclass
|
|
915
|
+
class TpyGlobal(TpyStmt):
|
|
916
|
+
"""global x, y -- declares names as referring to module-level variables."""
|
|
917
|
+
names: list[str]
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
@dataclass
|
|
921
|
+
class TpyNonlocal(TpyStmt):
|
|
922
|
+
"""nonlocal x, y -- declares names as mutable captures from enclosing scope."""
|
|
923
|
+
names: list[str]
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
@dataclass
|
|
927
|
+
class TpyRaise(TpyStmt):
|
|
928
|
+
"""raise E, raise E(args), raise <expr>, or bare raise (re-raise in except block)."""
|
|
929
|
+
exception_type: str | None = None # type-constructor form (set by parser, qualified by sema)
|
|
930
|
+
args: list[TpyExpr] = field(default_factory=list) # constructor arguments
|
|
931
|
+
raise_expr: TpyExpr | None = None # expression form: raise <expr> (throw-tier only)
|
|
932
|
+
is_call_form: bool = False # raise Name() vs raise Name (set by parser)
|
|
933
|
+
deref_depth: int = 0 # Set by sema: __deref__ steps to peel before calling __raise__ (Phase 20)
|
|
934
|
+
|
|
935
|
+
def exprs(self) -> list[TpyExpr]:
|
|
936
|
+
result = list(self.args)
|
|
937
|
+
if self.raise_expr is not None:
|
|
938
|
+
result.append(self.raise_expr)
|
|
939
|
+
return result
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
@dataclass
|
|
943
|
+
class TpyExceptHandler:
|
|
944
|
+
"""A single except clause in a try statement."""
|
|
945
|
+
exception_type: str | None # None for bare except:
|
|
946
|
+
binding: str | None # from "as e"
|
|
947
|
+
body: list[TpyStmt]
|
|
948
|
+
loc: SourceLocation | None = None
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
@dataclass
|
|
952
|
+
class TpyTry(TpyStmt):
|
|
953
|
+
"""try/except/else/finally statement (both return-tier and throw-tier)."""
|
|
954
|
+
try_body: list[TpyStmt]
|
|
955
|
+
handlers: list[TpyExceptHandler] # 0+ except clauses
|
|
956
|
+
else_body: list[TpyStmt] # may be empty
|
|
957
|
+
finally_body: list[TpyStmt] # may be empty
|
|
958
|
+
# Set by sema: "return" for ReturnException goto-based, "throw" for C++ try/catch
|
|
959
|
+
tier: Literal["return", "throw", "finally_only"] | None = None
|
|
960
|
+
|
|
961
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
962
|
+
bodies = [self.try_body, self.else_body, self.finally_body]
|
|
963
|
+
for h in self.handlers:
|
|
964
|
+
bodies.append(h.body)
|
|
965
|
+
return bodies
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
@dataclass
|
|
969
|
+
class TpyWithItem:
|
|
970
|
+
"""A single context manager in a with statement."""
|
|
971
|
+
context_expr: TpyExpr
|
|
972
|
+
target: str | None # as-variable name, or None if no `as`
|
|
973
|
+
loc: SourceLocation | None = None
|
|
974
|
+
# Set by sema: type returned by __enter__()
|
|
975
|
+
enter_type: TpyType | None = None
|
|
976
|
+
# Set by sema: True iff this ctx-manager's __exit__ returns bool (i.e.
|
|
977
|
+
# can suppress exceptions). False for None-returning __exit__.
|
|
978
|
+
exit_can_suppress: bool = False
|
|
979
|
+
# Set by sema: True iff exc_val is typed as Optional[BaseException]
|
|
980
|
+
# (i.e. the body inspects exceptions). False when exc_val: None.
|
|
981
|
+
# Codegen call site passes &__exc / nullptr when True, else {}.
|
|
982
|
+
exit_takes_exc_val: bool = False
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
@dataclass
|
|
986
|
+
class TpyWith(TpyStmt):
|
|
987
|
+
"""with statement (context managers).
|
|
988
|
+
|
|
989
|
+
`is_async=True` when this came from Python `async with`; the
|
|
990
|
+
context managers' `__aenter__` / `__aexit__` are async methods and
|
|
991
|
+
each call is a suspension point. Only allowed inside `async def`.
|
|
992
|
+
"""
|
|
993
|
+
items: list[TpyWithItem]
|
|
994
|
+
body: list[TpyStmt]
|
|
995
|
+
is_async: bool = False
|
|
996
|
+
|
|
997
|
+
def exprs(self) -> list[TpyExpr]:
|
|
998
|
+
return [item.context_expr for item in self.items]
|
|
999
|
+
|
|
1000
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
1001
|
+
return [self.body]
|
|
1002
|
+
|
|
1003
|
+
|
|
1004
|
+
@dataclass
|
|
1005
|
+
class TpyNestedDef(TpyStmt):
|
|
1006
|
+
"""Nested function definition inside a function body.
|
|
1007
|
+
|
|
1008
|
+
Compiles to a C++ lambda assigned to a local auto variable.
|
|
1009
|
+
Capture analysis is performed by sema.
|
|
1010
|
+
"""
|
|
1011
|
+
func: 'TpyFunction'
|
|
1012
|
+
# Set by sema:
|
|
1013
|
+
captured_names: list[str] = field(default_factory=list)
|
|
1014
|
+
nonlocal_names: set[str] = field(default_factory=set)
|
|
1015
|
+
escapes: bool = False
|
|
1016
|
+
# Captures that should use by-reference even in escaping closures
|
|
1017
|
+
# (non-value outer params whose original object outlives the closure)
|
|
1018
|
+
ref_captures: set[str] = field(default_factory=set)
|
|
1019
|
+
# Non-value locals to move into the closure (last use, no copy needed)
|
|
1020
|
+
move_captures: set[str] = field(default_factory=set)
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
# -- Pattern matching nodes --
|
|
1024
|
+
|
|
1025
|
+
@dataclass
|
|
1026
|
+
class TpyPattern:
|
|
1027
|
+
"""Base class for match/case patterns."""
|
|
1028
|
+
loc: SourceLocation | None = field(default=None, kw_only=True)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
@dataclass
|
|
1032
|
+
class TpyWildcardPattern(TpyPattern):
|
|
1033
|
+
"""case _:"""
|
|
1034
|
+
pass
|
|
1035
|
+
|
|
1036
|
+
|
|
1037
|
+
@dataclass
|
|
1038
|
+
class TpyCapturePattern(TpyPattern):
|
|
1039
|
+
"""case x:"""
|
|
1040
|
+
name: str
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
@dataclass
|
|
1044
|
+
class TpyClassPattern(TpyPattern):
|
|
1045
|
+
"""case Circle(): / case Circle(radius=r):"""
|
|
1046
|
+
cls: TpyExpr
|
|
1047
|
+
positional: list[TpyPattern]
|
|
1048
|
+
keywords: list[tuple[str, TpyPattern]]
|
|
1049
|
+
# Set by sema: resolved record type for this class pattern
|
|
1050
|
+
resolved_type: TpyType | None = None
|
|
1051
|
+
# Set by sema: True when this is a field sub-pattern matching against
|
|
1052
|
+
# a union-typed field (codegen emits holds_alternative + std::get)
|
|
1053
|
+
is_union_field_guard: bool = False
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
@dataclass
|
|
1057
|
+
class TpyLiteralPattern(TpyPattern):
|
|
1058
|
+
"""case 42: / case "hello": / case True: / case None:"""
|
|
1059
|
+
value: int | float | str | bool | None
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
@dataclass
|
|
1063
|
+
class TpyValuePattern(TpyPattern):
|
|
1064
|
+
"""case Color.RED: -- named constant via attribute access"""
|
|
1065
|
+
expr: TpyExpr
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
@dataclass
|
|
1069
|
+
class TpyOrPattern(TpyPattern):
|
|
1070
|
+
"""case Dog() | Cat():"""
|
|
1071
|
+
patterns: list[TpyPattern]
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
@dataclass
|
|
1075
|
+
class TpyAsPattern(TpyPattern):
|
|
1076
|
+
"""case P() as x:"""
|
|
1077
|
+
pattern: TpyPattern
|
|
1078
|
+
name: str
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
@dataclass
|
|
1082
|
+
class TpyMatchCase:
|
|
1083
|
+
"""A single case arm in a match statement."""
|
|
1084
|
+
pattern: TpyPattern
|
|
1085
|
+
guard: TpyExpr | None
|
|
1086
|
+
body: list[TpyStmt]
|
|
1087
|
+
loc: SourceLocation | None = None
|
|
1088
|
+
# Set by sema: narrowing facts for the subject variable in this arm
|
|
1089
|
+
type_facts: dict[str, TpyType] = field(default_factory=dict)
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
@dataclass
|
|
1093
|
+
class TpyMatch(TpyStmt):
|
|
1094
|
+
"""match/case statement."""
|
|
1095
|
+
subject: TpyExpr
|
|
1096
|
+
cases: list[TpyMatchCase]
|
|
1097
|
+
# Set by sema: resolved type of the subject expression
|
|
1098
|
+
subject_type: TpyType | None = None
|
|
1099
|
+
# Set by sema: true when the match covers every possible subject value
|
|
1100
|
+
# (either via a wildcard arm or by enumerating every value of a finite
|
|
1101
|
+
# type). Codegen relies on this to emit ``std::unreachable()`` past the
|
|
1102
|
+
# match end-label so non-void functions whose only exit is the match
|
|
1103
|
+
# don't trip -Wreturn-type.
|
|
1104
|
+
is_exhaustive: bool = False
|
|
1105
|
+
|
|
1106
|
+
def exprs(self) -> list[TpyExpr]:
|
|
1107
|
+
result: list[TpyExpr] = [self.subject]
|
|
1108
|
+
for case in self.cases:
|
|
1109
|
+
if case.guard is not None:
|
|
1110
|
+
result.append(case.guard)
|
|
1111
|
+
return result
|
|
1112
|
+
|
|
1113
|
+
def sub_bodies(self) -> list[list[TpyStmt]]:
|
|
1114
|
+
return [case.body for case in self.cases]
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
@dataclass(frozen=True)
|
|
1118
|
+
class RelativeImportKey:
|
|
1119
|
+
"""Structured key for relative import placeholders in import dicts.
|
|
1120
|
+
|
|
1121
|
+
Used as a temporary dict key before relative imports are resolved to
|
|
1122
|
+
canonical module names during discovery.
|
|
1123
|
+
"""
|
|
1124
|
+
# Prefix uses \x00 which cannot appear in a Python identifier,
|
|
1125
|
+
# making collision with real module names structurally impossible.
|
|
1126
|
+
_PREFIX = "\x00rel:"
|
|
1127
|
+
|
|
1128
|
+
level: int
|
|
1129
|
+
line: int
|
|
1130
|
+
col: int
|
|
1131
|
+
partial: str # Module name after dots ("" for bare "from . import X")
|
|
1132
|
+
|
|
1133
|
+
def encode(self) -> str:
|
|
1134
|
+
"""Encode as a unique string key for use in dicts."""
|
|
1135
|
+
return f"{self._PREFIX}{self.level}:{self.line}_{self.col}:{self.partial}"
|
|
1136
|
+
|
|
1137
|
+
@staticmethod
|
|
1138
|
+
def decode(key: str) -> 'RelativeImportKey':
|
|
1139
|
+
"""Decode a placeholder string back into structured fields."""
|
|
1140
|
+
# Format: \x00rel:{level}:{line}_{col}:{partial}
|
|
1141
|
+
body = key[len(RelativeImportKey._PREFIX):]
|
|
1142
|
+
level_str, loc, partial = body.split(":", 2)
|
|
1143
|
+
line_str, col_str = loc.split("_", 1)
|
|
1144
|
+
return RelativeImportKey(level=int(level_str), line=int(line_str), col=int(col_str), partial=partial)
|
|
1145
|
+
|
|
1146
|
+
@staticmethod
|
|
1147
|
+
def is_placeholder(key: str) -> bool:
|
|
1148
|
+
"""Check if a string key is a relative import placeholder."""
|
|
1149
|
+
return key.startswith(RelativeImportKey._PREFIX)
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
@dataclass
|
|
1153
|
+
class TpyImport(TpyStmt):
|
|
1154
|
+
"""Import statement for user modules.
|
|
1155
|
+
|
|
1156
|
+
Only user module imports (not builtins like tpy, typing) become TpyImport nodes.
|
|
1157
|
+
These are emitted as __tpy_init() calls in codegen.
|
|
1158
|
+
|
|
1159
|
+
For relative imports:
|
|
1160
|
+
- level: Number of dots (0=absolute, 1=".", 2="..", etc.)
|
|
1161
|
+
- relative_name: Original module name after dots (None for "from . import X")
|
|
1162
|
+
- module_name: Initially a RelativeImportKey.encode() placeholder, resolved during discovery
|
|
1163
|
+
|
|
1164
|
+
For aliased imports (import X as Y):
|
|
1165
|
+
- alias: The local name (Y) if different from module_name
|
|
1166
|
+
"""
|
|
1167
|
+
module_name: str
|
|
1168
|
+
level: int = 0
|
|
1169
|
+
relative_name: str | None = None
|
|
1170
|
+
alias: str | None = None
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
class RecordLinkage(Enum):
|
|
1174
|
+
"""Linkage mode for records (classes)."""
|
|
1175
|
+
DEFAULT = "default"
|
|
1176
|
+
NATIVE = "native" # C++ class import (fields only)
|
|
1177
|
+
NATIVE_C = "native_c" # C struct import (fields only)
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
class FunctionLinkage(Enum):
|
|
1181
|
+
"""Linkage mode for functions."""
|
|
1182
|
+
DEFAULT = "default"
|
|
1183
|
+
NATIVE = "native" # C++ import (stub, no body)
|
|
1184
|
+
NATIVE_C = "native_c" # C import (stub, no body)
|
|
1185
|
+
EXPORT_C = "export_c" # C export (has body)
|
|
1186
|
+
|
|
1187
|
+
|
|
1188
|
+
@dataclass
|
|
1189
|
+
class TpyFunction:
|
|
1190
|
+
"""Function definition.
|
|
1191
|
+
|
|
1192
|
+
For generic functions like def first[T](items: list[T]) -> T:
|
|
1193
|
+
- type_params stores the type parameter names (e.g., ["T"])
|
|
1194
|
+
- type_param_bounds stores bounds for each bounded type param (e.g., {"T": Comparable})
|
|
1195
|
+
"""
|
|
1196
|
+
name: str
|
|
1197
|
+
# Between parse and the post-parse resolve_refs pass, params
|
|
1198
|
+
# and return_type for top-level (non-method) functions may hold
|
|
1199
|
+
# TypeRefNode in place of TpyType. All readers post-pre-pass see
|
|
1200
|
+
# TpyType. `return_type` may also be None when no annotation was
|
|
1201
|
+
# provided -- sema substitutes VOID during the pre-pass.
|
|
1202
|
+
params: list[tuple[str, TpyType | TypeRefNode]]
|
|
1203
|
+
return_type: 'TpyType | TypeRefNode | None'
|
|
1204
|
+
body: list[TpyStmt]
|
|
1205
|
+
is_noalloc: bool = False
|
|
1206
|
+
is_inline: bool = False
|
|
1207
|
+
is_readonly: bool = False
|
|
1208
|
+
readonly_opt_out: bool = False
|
|
1209
|
+
is_pure: bool = False
|
|
1210
|
+
is_override: bool = False
|
|
1211
|
+
hides_parent: bool = False
|
|
1212
|
+
is_overload_stub: bool = False
|
|
1213
|
+
is_method: bool = False
|
|
1214
|
+
is_staticmethod: bool = False
|
|
1215
|
+
is_property_getter: bool = False
|
|
1216
|
+
is_property_setter: bool = False
|
|
1217
|
+
property_name: str | None = None # for setter: which property it belongs to
|
|
1218
|
+
is_consuming: bool = False
|
|
1219
|
+
# Transient input to sema.method_expansion._clone_auto_readonly:
|
|
1220
|
+
# true for @auto_readonly methods, @property getters, methods with
|
|
1221
|
+
# `self: auto_readonly[Self]`, and methods with per-param
|
|
1222
|
+
# `auto_readonly[T]`. After cloning, both clones carry False.
|
|
1223
|
+
auto_readonly: bool = False
|
|
1224
|
+
# Set on the mutable clone produced by
|
|
1225
|
+
# sema.method_expansion._clone_auto_readonly; used to detect
|
|
1226
|
+
# mutable+const clone pairs without relying on params-list identity.
|
|
1227
|
+
is_auto_readonly_mutable_clone: bool = False
|
|
1228
|
+
# Set on both clones after _clone_auto_readonly resolves AutoReadonlyType
|
|
1229
|
+
# in params. Tells sema/codegen not to blanket-apply readonly to all params
|
|
1230
|
+
# (each param already carries ReadonlyType or not from the clone).
|
|
1231
|
+
auto_readonly_params_resolved: bool = False
|
|
1232
|
+
# Transient input to sema.method_expansion._clone_auto_own: true when
|
|
1233
|
+
# `self: auto_own[Self]` is detected. After cloning, both clones have
|
|
1234
|
+
# auto_own=False.
|
|
1235
|
+
auto_own: bool = False
|
|
1236
|
+
# Set on the borrowing clone produced by
|
|
1237
|
+
# sema.method_expansion._clone_auto_own.
|
|
1238
|
+
is_auto_own_borrowing_clone: bool = False
|
|
1239
|
+
linkage: FunctionLinkage = FunctionLinkage.DEFAULT
|
|
1240
|
+
native_name: str | None = None
|
|
1241
|
+
native_function: bool = False
|
|
1242
|
+
native_preserves_refs: bool = False
|
|
1243
|
+
# @native(cpp_return_type=T) -- see FunctionInfo.native_cpp_return_type.
|
|
1244
|
+
native_cpp_return_type: str | None = None
|
|
1245
|
+
cpp_template: str | None = None
|
|
1246
|
+
is_stub: bool = False
|
|
1247
|
+
value_ptr_coercion: bool = False
|
|
1248
|
+
type_params: list[str] = field(default_factory=list)
|
|
1249
|
+
# Parallel with type_params (like TpyRecord.type_param_kinds). Carries
|
|
1250
|
+
# TypeParamKind.INT for `def f[N: int](...)` so sema-time resolution
|
|
1251
|
+
# of ref-based params can tell INT-kind type params from TYPE-kind.
|
|
1252
|
+
type_param_kinds: list[TypeParamKind] = field(default_factory=list)
|
|
1253
|
+
# Between parse and the post-parse resolve_refs pass, bounds
|
|
1254
|
+
# may hold TypeRefNode in place of TpyType. All readers post-pre-pass
|
|
1255
|
+
# see TpyType.
|
|
1256
|
+
type_param_bounds: 'dict[str, TpyType | TypeRefNode]' = field(default_factory=dict)
|
|
1257
|
+
type_param_defaults: dict[str, str] = field(default_factory=dict) # e.g. {"T": "tpy.extern.DefaultInt"}
|
|
1258
|
+
defaults: list['TpyExpr | None'] = field(default_factory=list) # len == len(params); None = no default
|
|
1259
|
+
keyword_only_start: int | None = None # index into params where keyword-only begins
|
|
1260
|
+
vararg_name: str | None = None # name of *args parameter
|
|
1261
|
+
vararg_type: 'TpyType | TypeRefNode | None' = None # element type T from *args: T
|
|
1262
|
+
kwarg_name: str | None = None # name of **kwargs parameter
|
|
1263
|
+
# Between parse and the post-parse resolve_refs pass, kwarg_type
|
|
1264
|
+
# may hold TypeRefNode in place of TpyType.
|
|
1265
|
+
kwarg_type: 'TpyType | TypeRefNode | None' = None # TypedDict type from **kwargs: Unpack[TD]
|
|
1266
|
+
error_return: str | None = None # @error_return(E) exception type name
|
|
1267
|
+
builtin_decorator_key: str | None = None # @builtin_decorator("tpy.readonly")
|
|
1268
|
+
builtin_function_key: str | None = None # @builtin_function("tpy.extern.native_global")
|
|
1269
|
+
is_generator: bool = False # Set by parser: body contains yield
|
|
1270
|
+
generator_yield_type: 'TpyType | None' = None # Set by sema: T from Iterator[T]
|
|
1271
|
+
generator_locals: 'list[tuple[str, TpyType]] | None' = None # Set by sema: local vars for struct fields
|
|
1272
|
+
is_async: bool = False # Set by parser: `async def`. Lowered to a state-machine
|
|
1273
|
+
# struct conforming to Awaitable[T] in PR 3 (codegen).
|
|
1274
|
+
# Set by `ClassInfo.add_method` for any function added through the macro
|
|
1275
|
+
# API. Lets sema diagnostics distinguish synthesized methods (e.g.
|
|
1276
|
+
# @dataclass __init__) from user-written ones, so messages can point the
|
|
1277
|
+
# user at the macro/decorator rather than at code they didn't write.
|
|
1278
|
+
is_macro_generated: bool = False
|
|
1279
|
+
# Optional name of the macro/decorator that produced this function
|
|
1280
|
+
# (e.g. "dataclass", "model", "total_ordering"). Set by macro modules
|
|
1281
|
+
# when calling `ClassInfo.add_method(..., macro_origin=...)`. Used by
|
|
1282
|
+
# diagnostics to name the responsible decorator in user-facing messages.
|
|
1283
|
+
macro_origin: str | None = None
|
|
1284
|
+
skip_codegen: bool = False # Set by sema: @inline function, body inlined at call sites
|
|
1285
|
+
# Preserves the self annotation for methods (e.g. OwnType(SelfType),
|
|
1286
|
+
# AutoOwnType(SelfType), AutoReadonlyType(SelfType)). Between parse
|
|
1287
|
+
# and the post-parse resolve_refs pass this may hold a
|
|
1288
|
+
# TypeRefNode; sema resolves before `method_expansion` reads it.
|
|
1289
|
+
# None when self had no explicit annotation or for non-methods.
|
|
1290
|
+
# Carried through `dc_replace` on auto_own / auto_readonly clones.
|
|
1291
|
+
self_annotation: 'TpyType | TypeRefNode | None' = None
|
|
1292
|
+
# Set by the parser when the method carries `@auto_readonly`.
|
|
1293
|
+
# `method_expansion` uses this to drive AutoReadonlyType param
|
|
1294
|
+
# wrapping; cleared on the auto_readonly clones once expanded.
|
|
1295
|
+
has_auto_readonly_decorator: bool = False
|
|
1296
|
+
loc: SourceLocation | None = None
|
|
1297
|
+
|
|
1298
|
+
@property
|
|
1299
|
+
def has_implementation(self) -> bool:
|
|
1300
|
+
"""True when this function/stub has its own implementation.
|
|
1301
|
+
|
|
1302
|
+
A function has an implementation if it carries a body, maps to an
|
|
1303
|
+
external C++ symbol (@native), or expands via a C++ template
|
|
1304
|
+
(@cpp_template). Plain bodyless stubs (``def f(x: int) -> int: ...``)
|
|
1305
|
+
are declaration-only and need a separate implementation.
|
|
1306
|
+
"""
|
|
1307
|
+
if not self.is_stub:
|
|
1308
|
+
return True
|
|
1309
|
+
return self.linkage != FunctionLinkage.DEFAULT or self.cpp_template is not None
|
|
1310
|
+
|
|
1311
|
+
@property
|
|
1312
|
+
def is_extern_c(self) -> bool:
|
|
1313
|
+
return self.linkage == FunctionLinkage.EXPORT_C
|
|
1314
|
+
|
|
1315
|
+
@property
|
|
1316
|
+
def is_export(self) -> bool:
|
|
1317
|
+
return self.linkage == FunctionLinkage.EXPORT_C
|
|
1318
|
+
|
|
1319
|
+
@property
|
|
1320
|
+
def extern_name(self) -> str | None:
|
|
1321
|
+
return self.native_name
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
@dataclass
|
|
1325
|
+
class TpyRecord:
|
|
1326
|
+
"""Record (class) definition.
|
|
1327
|
+
|
|
1328
|
+
For generic records like Stack[T]:
|
|
1329
|
+
- type_params stores the type parameter names (e.g., ["T"])
|
|
1330
|
+
- type_param_kinds stores the kind of each type param (TYPE or INT)
|
|
1331
|
+
- type_param_bounds stores bounds for each bounded type param (e.g., {"T": Comparable})
|
|
1332
|
+
|
|
1333
|
+
For generic records with integer type params like Matrix[T, N: int]:
|
|
1334
|
+
- type_params = ["T", "N"]
|
|
1335
|
+
- type_param_kinds = [TYPE, INT]
|
|
1336
|
+
|
|
1337
|
+
For class inheritance:
|
|
1338
|
+
- bases stores the parsed base types (classes or protocols)
|
|
1339
|
+
- Classification into parent class vs protocol implementations is done in sema
|
|
1340
|
+
"""
|
|
1341
|
+
name: str
|
|
1342
|
+
fields: list[FieldInfo]
|
|
1343
|
+
methods: list[TpyFunction] = field(default_factory=list)
|
|
1344
|
+
type_params: list[str] = field(default_factory=list)
|
|
1345
|
+
type_param_kinds: list[TypeParamKind] = field(default_factory=list)
|
|
1346
|
+
# Between parse and the post-parse resolve_refs pass, bounds
|
|
1347
|
+
# may hold TypeRefNode in place of TpyType. All readers post-pre-pass
|
|
1348
|
+
# see TpyType.
|
|
1349
|
+
type_param_bounds: 'dict[str, TpyType | TypeRefNode]' = field(default_factory=dict)
|
|
1350
|
+
# Between parse and the post-parse resolve_refs pass, bases
|
|
1351
|
+
# may hold TypeRefNode in place of TpyType. All readers post-pre-
|
|
1352
|
+
# pass see TpyType.
|
|
1353
|
+
bases: 'list[TpyType | TypeRefNode]' = field(default_factory=list)
|
|
1354
|
+
linkage: RecordLinkage = RecordLinkage.DEFAULT
|
|
1355
|
+
native_name: str | None = None
|
|
1356
|
+
is_nocopy: bool = False
|
|
1357
|
+
is_frozen: bool = False
|
|
1358
|
+
is_typed_dict: bool = False
|
|
1359
|
+
is_total_false: bool = False # TypedDict(total=False): all fields Optional
|
|
1360
|
+
builtin_type_key: str | None = None
|
|
1361
|
+
# @native(indirecting=True): record owns indirect (heap-backed) storage
|
|
1362
|
+
# of its type parameter that the compiler cannot introspect (e.g. list/
|
|
1363
|
+
# dict/set, or a user @native record wrapping a C++ unique_ptr).
|
|
1364
|
+
# Consulted by cycle detection so the type breaks recursive size cycles.
|
|
1365
|
+
# User TPy records with a Ptr[T] field do NOT set this -- the field walk
|
|
1366
|
+
# infers indirection structurally.
|
|
1367
|
+
is_indirecting: bool = False
|
|
1368
|
+
pending_macros: list[tuple[str, dict[str, Any]]] = field(default_factory=list)
|
|
1369
|
+
# Callbacks registered via `ClassInfo.defer_until_macros_complete()`.
|
|
1370
|
+
# Run after the eager macro pass so a macro can inspect the final
|
|
1371
|
+
# method set produced by composition (e.g. @total_ordering picking
|
|
1372
|
+
# an anchor among ordering ops other macros may add).
|
|
1373
|
+
pending_deferred_macros: list[Any] = field(default_factory=list)
|
|
1374
|
+
nested_records: list[TpyRecord] = field(default_factory=list)
|
|
1375
|
+
nested_enums: list[TpyEnum] = field(default_factory=list)
|
|
1376
|
+
loc: SourceLocation | None = None
|
|
1377
|
+
|
|
1378
|
+
@property
|
|
1379
|
+
def init_method(self) -> Optional[TpyFunction]:
|
|
1380
|
+
"""Get __init__ method if present."""
|
|
1381
|
+
for m in self.methods:
|
|
1382
|
+
if m.name == "__init__":
|
|
1383
|
+
return m
|
|
1384
|
+
return None
|
|
1385
|
+
|
|
1386
|
+
@property
|
|
1387
|
+
def del_method(self) -> Optional[TpyFunction]:
|
|
1388
|
+
"""Get __del__ method if present."""
|
|
1389
|
+
for m in self.methods:
|
|
1390
|
+
if m.name == "__del__":
|
|
1391
|
+
return m
|
|
1392
|
+
return None
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
@dataclass
|
|
1396
|
+
class TpyProtocol:
|
|
1397
|
+
"""Protocol definition for structural subtyping."""
|
|
1398
|
+
name: str
|
|
1399
|
+
methods: list[MethodSignature]
|
|
1400
|
+
# Between parse and the post-parse resolve_refs pass, field
|
|
1401
|
+
# types may hold TypeRefNode in place of TpyType.
|
|
1402
|
+
fields: 'list[tuple[str, TpyType | TypeRefNode]]' = field(default_factory=list)
|
|
1403
|
+
type_params: list[str] = field(default_factory=list)
|
|
1404
|
+
# Parent protocols. Parser emits TypeRefNode (TpyTypeRef) entries; sema's
|
|
1405
|
+
# resolve_refs pass replaces them in place with NominalType. Generic
|
|
1406
|
+
# parents like `Iterable[T]` carry their type args in NominalType.type_args.
|
|
1407
|
+
parent_protocols: 'list[NominalType | TypeRefNode]' = field(default_factory=list)
|
|
1408
|
+
is_dynamic: bool = False
|
|
1409
|
+
cpp_concept: str | None = None
|
|
1410
|
+
loc: SourceLocation | None = None
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
@dataclass
|
|
1414
|
+
class TpyEnum:
|
|
1415
|
+
"""Enum definition -- symbolic constants grouped under a named type."""
|
|
1416
|
+
name: str
|
|
1417
|
+
members: list[tuple[str, int, SourceLocation | None]] # auto() already resolved to int by parser
|
|
1418
|
+
is_int_enum: bool = False
|
|
1419
|
+
underlying_type_name: str | None = None # e.g. "int", "Int8", "UInt32"
|
|
1420
|
+
is_native: bool = False
|
|
1421
|
+
native_name: str | None = None # raw @native arg; sema normalizes to canonical ::-prefixed form
|
|
1422
|
+
# Per-member C++ rename map (Python name -> C++ enumerator name). Used
|
|
1423
|
+
# for @native enums where the C++ side has a name TPy can't spell
|
|
1424
|
+
# (Python keywords like `None`) or uses a different convention.
|
|
1425
|
+
# Members without an entry use the Python name as the C++ name.
|
|
1426
|
+
cpp_member_names: dict[str, str] = field(default_factory=dict)
|
|
1427
|
+
# True when the user spelled at least one explicit integer literal as a
|
|
1428
|
+
# member value. For @native enums this triggers per-member static_assert
|
|
1429
|
+
# emission so the TPy-declared value is verified against the C++ side at
|
|
1430
|
+
# compile time. False when all members used auto() / native_member()
|
|
1431
|
+
# (the user opted out of declaring values; C++ is the truth).
|
|
1432
|
+
has_explicit_values: bool = False
|
|
1433
|
+
loc: SourceLocation | None = None
|
|
1434
|
+
|
|
1435
|
+
|
|
1436
|
+
@dataclass
|
|
1437
|
+
class ParseWarning:
|
|
1438
|
+
"""A warning generated during parsing."""
|
|
1439
|
+
message: str
|
|
1440
|
+
loc: SourceLocation | None
|
|
1441
|
+
|
|
1442
|
+
|
|
1443
|
+
@dataclass
|
|
1444
|
+
class ModuleDirectives:
|
|
1445
|
+
"""Module-level compiler directives from # tpy: comments."""
|
|
1446
|
+
# Each entry: (include_path, platform_filter_or_None)
|
|
1447
|
+
includes: list[tuple[str, str | None]] = field(default_factory=list)
|
|
1448
|
+
# Each entry: (lib_name, platform_filter_or_None)
|
|
1449
|
+
link_libs: list[tuple[str, str | None]] = field(default_factory=list)
|
|
1450
|
+
# Third-party library declarations: (registry_name, platform_filter).
|
|
1451
|
+
# Resolved at build time via tpyc.build.third_party into include paths,
|
|
1452
|
+
# link flags, and CMake snippets based on the user's selected mode.
|
|
1453
|
+
third_party_deps: list[tuple[str, str | None]] = field(default_factory=list)
|
|
1454
|
+
native_module: bool = False
|
|
1455
|
+
# Override C++ namespace (replaces tpyapp::module_name)
|
|
1456
|
+
cpp_namespace: str | None = None
|
|
1457
|
+
|
|
1458
|
+
|
|
1459
|
+
@dataclass
|
|
1460
|
+
class TpyModule:
|
|
1461
|
+
"""Top-level module."""
|
|
1462
|
+
records: list[TpyRecord]
|
|
1463
|
+
functions: list[TpyFunction]
|
|
1464
|
+
protocols: list[TpyProtocol] = field(default_factory=list)
|
|
1465
|
+
enums: list[TpyEnum] = field(default_factory=list)
|
|
1466
|
+
top_level_stmts: list[TpyStmt] = field(default_factory=list)
|
|
1467
|
+
source_lines: list[str] = field(default_factory=list) # Original source lines for source mapping
|
|
1468
|
+
# Import tracking: module_name -> set of (original_name, local_name) tuples (for "from X import Y as Z")
|
|
1469
|
+
# module_name -> None (for "import X")
|
|
1470
|
+
imports: dict[str, set[tuple[str, str]] | None] = field(default_factory=dict)
|
|
1471
|
+
# True when "from tpy import *" was used (triggers full tpy registration in sema)
|
|
1472
|
+
tpy_star_import: bool = False
|
|
1473
|
+
# Set of module names that had 'from X import *'
|
|
1474
|
+
star_imports: set[str] = field(default_factory=set)
|
|
1475
|
+
# Module's own `__all__` literal, captured at parse time. `None` when
|
|
1476
|
+
# the module does not define `__all__`; consumers treat that as "no
|
|
1477
|
+
# filter applied beyond the leading-underscore rule".
|
|
1478
|
+
module_all: frozenset[str] | None = None
|
|
1479
|
+
# Source location of the `__all__` literal (the assignment's RHS), used
|
|
1480
|
+
# for diagnostics that point at the `__all__` declaration -- e.g. the
|
|
1481
|
+
# phantom-name warning when `__all__` lists a name the module does not
|
|
1482
|
+
# actually export. None whenever `module_all` is None.
|
|
1483
|
+
module_all_loc: SourceLocation | None = None
|
|
1484
|
+
# Modules to resolve as files: {module_name: line_number}
|
|
1485
|
+
user_module_imports: dict[str, int] = field(default_factory=dict)
|
|
1486
|
+
# Module aliases from "from . import submod" -> {canonical_name: local_name}
|
|
1487
|
+
module_aliases: dict[str, str] = field(default_factory=dict)
|
|
1488
|
+
# Modules that had bare `import X` statements (needed for module binding in sema)
|
|
1489
|
+
bare_module_imports: set[str] = field(default_factory=set)
|
|
1490
|
+
# Type aliases (e.g., Shape = Circle | Rect) -> (resolved type, source
|
|
1491
|
+
# location, type_params, type_param_kinds). `type_params` /
|
|
1492
|
+
# `type_param_kinds` are non-empty only for generic aliases
|
|
1493
|
+
# (`type Tree[T] = ...`); v1 of generic aliases keeps storage
|
|
1494
|
+
# extensible without consuming type_params yet -- see
|
|
1495
|
+
# `docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md`.
|
|
1496
|
+
# Between parse and the post-parse resolve_refs pass, alias
|
|
1497
|
+
# RHS values may hold TypeRefNode in place of TpyType. Sema resolves
|
|
1498
|
+
# each alias passing `pending_alias=alias_name` through the resolver
|
|
1499
|
+
# API so same-body self-refs become NominalType(name) placeholders,
|
|
1500
|
+
# then detects recursive unions post-resolution.
|
|
1501
|
+
type_aliases: 'dict[str, tuple[TpyType | TypeRefNode, SourceLocation | None, list[str], list[TypeParamKind]]]' = field(default_factory=dict)
|
|
1502
|
+
# Parser warnings (e.g., imports after non-import code)
|
|
1503
|
+
parse_warnings: list[ParseWarning] = field(default_factory=list)
|
|
1504
|
+
# Module-level # tpy: directives
|
|
1505
|
+
directives: ModuleDirectives = field(default_factory=ModuleDirectives)
|
|
1506
|
+
# Union type aliases that need wrapper-struct representation (self- or mutually-recursive)
|
|
1507
|
+
recursive_union_names: set[str] = field(default_factory=set)
|
|
1508
|
+
# Parser resolver: a TypeResolver instance attached at end-of-parse
|
|
1509
|
+
# that resolves a TypeRefNode (emitted by the walker at annotation
|
|
1510
|
+
# sites) to a TpyType. Sema invokes `resolver.resolve(ref, scope)`
|
|
1511
|
+
# via `TypeOperations.resolve_type_ref` when a writer encounters an
|
|
1512
|
+
# unresolved ref. Holds a back-reference to the parser so it carries
|
|
1513
|
+
# live resolution state (registry, imports, local_defs,
|
|
1514
|
+
# module_class_names, type_alias_names, reverse_module_aliases,
|
|
1515
|
+
# bare_module_imports).
|
|
1516
|
+
resolver: 'Any | None' = None
|
|
1517
|
+
|
|
1518
|
+
def all_records(self) -> list[TpyRecord]:
|
|
1519
|
+
"""All records including nested, in definition order (depth-first)."""
|
|
1520
|
+
result: list[TpyRecord] = []
|
|
1521
|
+
def collect(records: list[TpyRecord]) -> None:
|
|
1522
|
+
for r in records:
|
|
1523
|
+
result.append(r)
|
|
1524
|
+
collect(r.nested_records)
|
|
1525
|
+
collect(self.records)
|
|
1526
|
+
return result
|
|
1527
|
+
|
|
1528
|
+
def all_enums(self) -> list[TpyEnum]:
|
|
1529
|
+
"""All enums including those nested inside records, in definition order."""
|
|
1530
|
+
result: list[TpyEnum] = list(self.enums)
|
|
1531
|
+
def collect(records: list[TpyRecord]) -> None:
|
|
1532
|
+
for r in records:
|
|
1533
|
+
result.extend(r.nested_enums)
|
|
1534
|
+
collect(r.nested_records)
|
|
1535
|
+
collect(self.records)
|
|
1536
|
+
return result
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
def is_docstring(stmt: TpyStmt) -> bool:
|
|
1540
|
+
"""True for a bare string-literal expression statement -- i.e. the
|
|
1541
|
+
syntactic form Python recognises as a docstring at the top of a
|
|
1542
|
+
function/method/class/module body.
|
|
1543
|
+
"""
|
|
1544
|
+
return isinstance(stmt, TpyExprStmt) and isinstance(stmt.expr, TpyStrLiteral)
|
|
1545
|
+
|
|
1546
|
+
|
|
1547
|
+
def is_super_del_call(stmt: TpyStmt) -> bool:
|
|
1548
|
+
"""Check if a statement is a super().__del__() call."""
|
|
1549
|
+
if isinstance(stmt, TpyExprStmt):
|
|
1550
|
+
expr = stmt.expr
|
|
1551
|
+
if isinstance(expr, TpyMethodCall) and expr.method == "__del__":
|
|
1552
|
+
return expr.super_parent_type is not None
|
|
1553
|
+
return False
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
def is_base_init_call(stmt: TpyStmt) -> bool:
|
|
1557
|
+
"""True for super().__init__(...) or BaseN.__init__(self, ...). Both forms
|
|
1558
|
+
belong in the init section and hoist into the C++ member initializer list.
|
|
1559
|
+
"""
|
|
1560
|
+
if isinstance(stmt, TpyExprStmt):
|
|
1561
|
+
expr = stmt.expr
|
|
1562
|
+
if isinstance(expr, TpyMethodCall) and expr.method == "__init__":
|
|
1563
|
+
return (expr.super_parent_type is not None
|
|
1564
|
+
or expr.unbound_self_parent_type is not None)
|
|
1565
|
+
return False
|
|
1566
|
+
|
|
1567
|
+
|
|
1568
|
+
def collect_name_refs(expr: TpyExpr) -> set[str]:
|
|
1569
|
+
"""Collect all name references in an expression tree.
|
|
1570
|
+
|
|
1571
|
+
Uses TpyExpr.children() for generic traversal. TpyCall.func is a
|
|
1572
|
+
TpyName child, so callable variable names are captured automatically.
|
|
1573
|
+
"""
|
|
1574
|
+
names: set[str] = set()
|
|
1575
|
+
stack: list[TpyExpr] = [expr]
|
|
1576
|
+
while stack:
|
|
1577
|
+
node = stack.pop()
|
|
1578
|
+
if isinstance(node, TpyName):
|
|
1579
|
+
names.add(node.name)
|
|
1580
|
+
else:
|
|
1581
|
+
stack.extend(node.children())
|
|
1582
|
+
return names
|
|
1583
|
+
|
|
1584
|
+
|
|
1585
|
+
def expr_reads_self_field(expr: TpyExpr, fields: set[str]) -> bool:
|
|
1586
|
+
"""True if `expr` reads `self.X` for any X in `fields`, or makes any
|
|
1587
|
+
`self.method(...)` call while `fields` is non-empty (methods can read
|
|
1588
|
+
arbitrary fields, so we treat them conservatively).
|
|
1589
|
+
|
|
1590
|
+
Intended for ordering checks between an own-field MIL hoist and prior
|
|
1591
|
+
body writes to `self.*`. Not a general-purpose aliasing oracle -- the
|
|
1592
|
+
method-call rule is over-conservative for that one use case.
|
|
1593
|
+
|
|
1594
|
+
See also `expr_contains_self_method_call` -- the method-only variant
|
|
1595
|
+
(no field-name set, filters out static-method calls). Pick that one
|
|
1596
|
+
when you only care whether any instance method on self was invoked.
|
|
1597
|
+
"""
|
|
1598
|
+
if not fields:
|
|
1599
|
+
return False
|
|
1600
|
+
stack: list[TpyExpr] = [expr]
|
|
1601
|
+
while stack:
|
|
1602
|
+
node = stack.pop()
|
|
1603
|
+
if isinstance(node, TpyFieldAccess):
|
|
1604
|
+
if isinstance(node.obj, TpyName) and node.obj.name == "self":
|
|
1605
|
+
if node.field in fields:
|
|
1606
|
+
return True
|
|
1607
|
+
elif isinstance(node, TpyMethodCall):
|
|
1608
|
+
if isinstance(node.obj, TpyName) and node.obj.name == "self":
|
|
1609
|
+
return True
|
|
1610
|
+
stack.extend(node.children())
|
|
1611
|
+
return False
|
|
1612
|
+
|
|
1613
|
+
|
|
1614
|
+
def expr_contains_self_method_call(expr: TpyExpr) -> bool:
|
|
1615
|
+
"""True if `expr` contains any non-static `self.method(...)` call.
|
|
1616
|
+
|
|
1617
|
+
Used by __init__ analysis to flag method calls in field-init RHS that
|
|
1618
|
+
might observe still-uninitialized fields.
|
|
1619
|
+
|
|
1620
|
+
See also `expr_reads_self_field` -- combines field-read detection
|
|
1621
|
+
(for a caller-supplied set of field names) with method-call
|
|
1622
|
+
detection. Pick that one when you also need to know if specific
|
|
1623
|
+
self.X reads occur, not just method calls.
|
|
1624
|
+
"""
|
|
1625
|
+
stack: list[TpyExpr] = [expr]
|
|
1626
|
+
while stack:
|
|
1627
|
+
node = stack.pop()
|
|
1628
|
+
if isinstance(node, TpyMethodCall):
|
|
1629
|
+
if (isinstance(node.obj, TpyName)
|
|
1630
|
+
and node.obj.name == "self"
|
|
1631
|
+
and not node.is_static_call):
|
|
1632
|
+
return True
|
|
1633
|
+
stack.extend(node.children())
|
|
1634
|
+
return False
|
|
1635
|
+
|
|
1636
|
+
|
|
1637
|
+
def collect_top_level_local_names(stmts: list[TpyStmt]) -> set[str]:
|
|
1638
|
+
"""Collect names bound by top-level statements in a function body.
|
|
1639
|
+
|
|
1640
|
+
Covers the forms that introduce locals at this scope: `x: T = ...`
|
|
1641
|
+
(TpyVarDecl), `x = ...` (TpyAssign with a bare-name target), and
|
|
1642
|
+
`a, b = ...` (TpyTupleUnpack). Does NOT descend into control flow:
|
|
1643
|
+
names bound inside `if`, `for`, `while`, etc. are not included --
|
|
1644
|
+
callers reasoning about the top-level MIL/init split only need
|
|
1645
|
+
names visible at statement depth 0.
|
|
1646
|
+
"""
|
|
1647
|
+
names: set[str] = set()
|
|
1648
|
+
for s in stmts:
|
|
1649
|
+
if isinstance(s, TpyVarDecl):
|
|
1650
|
+
names.add(s.name)
|
|
1651
|
+
elif isinstance(s, TpyAssign) and isinstance(s.target, TpyName):
|
|
1652
|
+
names.add(s.target.name)
|
|
1653
|
+
elif isinstance(s, TpyTupleUnpack):
|
|
1654
|
+
names.update(t for t in s.targets if t is not None)
|
|
1655
|
+
return names
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
def is_stable_address_lvalue(expr: TpyExpr) -> bool:
|
|
1659
|
+
"""True iff `expr` resolves to a stable lvalue: a bare name (local /
|
|
1660
|
+
parameter / frame field) or a chain of field accesses rooted at a
|
|
1661
|
+
name. Subscripts, calls, binops, conditional / comprehension
|
|
1662
|
+
expressions return temporaries whose address would dangle across
|
|
1663
|
+
an async suspension or a stored borrow.
|
|
1664
|
+
|
|
1665
|
+
Stricter than `sema/compatibility.py::is_lvalue`: subscripts
|
|
1666
|
+
(addressable in C++ but yield container-element temporaries
|
|
1667
|
+
across suspensions) and `TpyCoerce` (sema-only wrapper, doesn't
|
|
1668
|
+
reach codegen) are both excluded.
|
|
1669
|
+
|
|
1670
|
+
Used by:
|
|
1671
|
+
* `sema/expressions.py::analyze_await` to reject
|
|
1672
|
+
`await rvalue.method()` (M4 async-method receiver capture).
|
|
1673
|
+
* `codegen_cpp/gen_async.py::_make_await_payload` to choose
|
|
1674
|
+
BORROWED vs ERASED await mode.
|
|
1675
|
+
"""
|
|
1676
|
+
e = expr
|
|
1677
|
+
while isinstance(e, TpyFieldAccess):
|
|
1678
|
+
e = e.obj
|
|
1679
|
+
return isinstance(e, TpyName)
|
|
1680
|
+
|
|
1681
|
+
|
|
1682
|
+
def stmt_has_any_suspension(stmt: TpyStmt) -> bool:
|
|
1683
|
+
"""Walk a statement (its expression slots and sub_bodies) for any
|
|
1684
|
+
suspension point -- a `TpyAwait` (async) or a `TpyYield` (generator).
|
|
1685
|
+
`async with` and `async for` always count even when their bodies have
|
|
1686
|
+
none: the `__aenter__` / `__aexit__` / `__anext__` calls are themselves
|
|
1687
|
+
suspensions. Does not descend into nested function/lambda bodies (a
|
|
1688
|
+
suspension there belongs to the inner callable, not this one)."""
|
|
1689
|
+
if isinstance(stmt, TpyYield):
|
|
1690
|
+
return True
|
|
1691
|
+
if isinstance(stmt, TpyWith) and stmt.is_async:
|
|
1692
|
+
return True
|
|
1693
|
+
if isinstance(stmt, TpyForEach) and stmt.is_async:
|
|
1694
|
+
return True
|
|
1695
|
+
|
|
1696
|
+
def walk_expr(e: TpyExpr | None) -> bool:
|
|
1697
|
+
if e is None:
|
|
1698
|
+
return False
|
|
1699
|
+
if isinstance(e, TpyAwait):
|
|
1700
|
+
return True
|
|
1701
|
+
for c in (e.children() if hasattr(e, "children") else ()):
|
|
1702
|
+
if walk_expr(c):
|
|
1703
|
+
return True
|
|
1704
|
+
return False
|
|
1705
|
+
|
|
1706
|
+
if hasattr(stmt, "exprs"):
|
|
1707
|
+
for e in stmt.exprs():
|
|
1708
|
+
if walk_expr(e):
|
|
1709
|
+
return True
|
|
1710
|
+
if hasattr(stmt, "sub_bodies"):
|
|
1711
|
+
for body in stmt.sub_bodies():
|
|
1712
|
+
for s in body:
|
|
1713
|
+
if stmt_has_any_suspension(s):
|
|
1714
|
+
return True
|
|
1715
|
+
return False
|
|
1716
|
+
|
|
1717
|
+
|
|
1718
|
+
def stmts_have_any_suspension(stmts: list[TpyStmt]) -> bool:
|
|
1719
|
+
return any(stmt_has_any_suspension(s) for s in stmts)
|
|
1720
|
+
|
|
1721
|
+
|
|
1722
|
+
def stmts_have_any_return(stmts: list[TpyStmt]) -> bool:
|
|
1723
|
+
"""True if any of `stmts` (recursively through sub_bodies) contains a
|
|
1724
|
+
TpyReturn. Does not descend into nested function/lambda bodies."""
|
|
1725
|
+
for s in stmts:
|
|
1726
|
+
if isinstance(s, TpyReturn):
|
|
1727
|
+
return True
|
|
1728
|
+
if hasattr(s, "sub_bodies"):
|
|
1729
|
+
for b in s.sub_bodies():
|
|
1730
|
+
if stmts_have_any_return(b):
|
|
1731
|
+
return True
|
|
1732
|
+
return False
|