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/macro_api.py
ADDED
|
@@ -0,0 +1,1724 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Macro API
|
|
3
|
+
|
|
4
|
+
Public API for macro modules. Macro modules import from this package to inspect
|
|
5
|
+
and modify class definitions during compilation.
|
|
6
|
+
|
|
7
|
+
Macro modules are normal .py files with a ``# tpy: macro_module`` directive.
|
|
8
|
+
They are executed via CPython during compilation (not compiled to C++).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import copy
|
|
14
|
+
import textwrap
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any, Callable, Literal, NoReturn, TYPE_CHECKING
|
|
17
|
+
|
|
18
|
+
from .parse.nodes import ParseError as _ParseError
|
|
19
|
+
from .typesys import (
|
|
20
|
+
TpyType, NominalType, OwnType,
|
|
21
|
+
OptionalType,
|
|
22
|
+
TupleType, UnionType, make_set, make_dict, make_list, make_span,
|
|
23
|
+
FieldInfo as InternalFieldInfo,
|
|
24
|
+
INT8, INT16, INT32, INT64, UINT8, UINT16, UINT32, UINT64,
|
|
25
|
+
ALL_FIXED_INTS,
|
|
26
|
+
is_float_type,
|
|
27
|
+
unwrap_final,
|
|
28
|
+
)
|
|
29
|
+
from .type_def_registry import is_str_type, is_str_view_type
|
|
30
|
+
from .symbol_binding import SymbolKind, lookup_imported
|
|
31
|
+
from .typesys import (
|
|
32
|
+
VOID as _VOID, STR as _STR, STRVIEW as _STRVIEW, BOOL as _BOOL,
|
|
33
|
+
FLOAT as _FLOAT, FLOAT32 as _FLOAT32, BIGINT as _BIGINT,
|
|
34
|
+
)
|
|
35
|
+
from .parse.parser import FragmentParser as _FragmentParser
|
|
36
|
+
from .parse import (
|
|
37
|
+
TpyRecord, TpyFunction, TpyExpr, TpyStmt,
|
|
38
|
+
TpyAssign, TpyFieldAccess, TpyName, TpyBinOp, TpyReturn,
|
|
39
|
+
TpyMethodCall, TpyCall, TpyExprStmt,
|
|
40
|
+
TpyIntLiteral, TpyFloatLiteral, TpyBoolLiteral, TpyStrLiteral,
|
|
41
|
+
TpyNoneLiteral, TpyUnaryOp,
|
|
42
|
+
TpyVarDecl, TpyTupleUnpack, TpyIf, TpyWhile, TpyForEach,
|
|
43
|
+
TpyRaise, TpyAssert, TpyExceptHandler, TpyTry,
|
|
44
|
+
TpyMatch, TpyMatchCase, TpyLiteralPattern, TpyWildcardPattern,
|
|
45
|
+
TpySubscript, TpyTupleLiteral, TpyArrayLiteral, TpyDictLiteral,
|
|
46
|
+
TpyListComprehension, TpyComprehensionGenerator, TpyDictComprehension,
|
|
47
|
+
TpySetComprehension, TpyIfExpr,
|
|
48
|
+
TpyFString, TpyFStringValue,
|
|
49
|
+
TpyPattern,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Public type aliases for macro module type annotations
|
|
53
|
+
Expr = TpyExpr
|
|
54
|
+
Stmt = TpyStmt
|
|
55
|
+
Function = TpyFunction
|
|
56
|
+
Type = TpyType
|
|
57
|
+
MatchCase = TpyMatchCase
|
|
58
|
+
ExceptHandler = TpyExceptHandler
|
|
59
|
+
Pattern = TpyPattern
|
|
60
|
+
ComprehensionGenerator = TpyComprehensionGenerator
|
|
61
|
+
|
|
62
|
+
# Re-exports from the tpyc package so macro modules only need to import
|
|
63
|
+
# from tpyc.macro_api.
|
|
64
|
+
from . import __version__, VERSION_INFO
|
|
65
|
+
|
|
66
|
+
if TYPE_CHECKING:
|
|
67
|
+
from collections.abc import Iterator
|
|
68
|
+
from .sema.context import SemanticContext
|
|
69
|
+
from .typesys import RecordInfo
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
_FIXED_INT_NAMES: frozenset[str] = frozenset(str(t) for t in ALL_FIXED_INTS)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# MacroError -- raised by macros to report compile errors
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
class MacroError(Exception):
|
|
80
|
+
"""Raised by macro code to report a compile error.
|
|
81
|
+
|
|
82
|
+
The macro infrastructure catches this and converts it to a SemanticError
|
|
83
|
+
with the appropriate source location. If ``loc`` is provided, it overrides
|
|
84
|
+
the default location (the decorator/call site).
|
|
85
|
+
"""
|
|
86
|
+
def __init__(self, msg: str, loc: Any = None) -> None:
|
|
87
|
+
super().__init__(msg)
|
|
88
|
+
self.loc = loc
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ---------------------------------------------------------------------------
|
|
92
|
+
# Decorator for marking macro functions
|
|
93
|
+
# ---------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def class_macro(fn: Callable) -> Callable:
|
|
96
|
+
"""Mark a function as a class macro.
|
|
97
|
+
|
|
98
|
+
Class macros receive a ClassInfo and modify it (add methods, set flags).
|
|
99
|
+
"""
|
|
100
|
+
fn._is_class_macro = True
|
|
101
|
+
return fn
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def call_macro(fn: Callable) -> Callable:
|
|
105
|
+
"""Mark a function as a call-site macro.
|
|
106
|
+
|
|
107
|
+
Call macros receive a CallMacroContext + MacroArg per argument.
|
|
108
|
+
They return a replacement TpyExpr that sema analyzes.
|
|
109
|
+
"""
|
|
110
|
+
fn._is_call_macro = True
|
|
111
|
+
return fn
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Builder-trace macros (Phase 7)
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
# A @builder_macro class describes a state machine that the compiler walks
|
|
118
|
+
# while analyzing a function body. The compiler tracks the symbol bound by
|
|
119
|
+
# the constructor and dispatches subsequent method calls on it to the
|
|
120
|
+
# class's @builder_method / @builder_returns / @builder_terminal handlers.
|
|
121
|
+
# A terminal closes the trace by emitting synthesized declarations and
|
|
122
|
+
# rewriting the original call site.
|
|
123
|
+
|
|
124
|
+
def builder_macro(cls: type) -> type:
|
|
125
|
+
"""Mark a class as a builder-trace macro.
|
|
126
|
+
|
|
127
|
+
The class's ``__init__(self, ctx: BuilderContext, args: MacroArgs)`` is
|
|
128
|
+
invoked when the compiler sees a constructor call to it. Methods marked
|
|
129
|
+
``@builder_method`` / ``@builder_returns(child)`` / ``@builder_terminal``
|
|
130
|
+
are dispatched on subsequent method calls of the bound symbol.
|
|
131
|
+
"""
|
|
132
|
+
cls._is_builder_macro = True
|
|
133
|
+
return cls
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def builder_method(fn: Callable) -> Callable:
|
|
137
|
+
"""Mark a method as a void builder step that mutates state."""
|
|
138
|
+
fn._is_builder_method = True
|
|
139
|
+
return fn
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def builder_returns(child_class: type) -> Callable[[Callable], Callable]:
|
|
143
|
+
"""Mark a method as returning a sub-builder.
|
|
144
|
+
|
|
145
|
+
The decorated method's return value (a fresh ``child_class`` instance)
|
|
146
|
+
becomes a new tracked symbol in the trace, allowing chained builder
|
|
147
|
+
calls.
|
|
148
|
+
|
|
149
|
+
The expander dispatches sub-builder method calls using the handlers
|
|
150
|
+
declared on the *declared* ``child_class`` -- subclass dispatch is
|
|
151
|
+
not supported. If a handler returns an instance of a subclass of
|
|
152
|
+
``child_class``, methods declared only on the subclass will not be
|
|
153
|
+
found by the expander. Always declare the exact return type.
|
|
154
|
+
|
|
155
|
+
The LHS name receiving the sub-builder must not already be a
|
|
156
|
+
tracked builder symbol; reassignment is rejected.
|
|
157
|
+
"""
|
|
158
|
+
def decorate(fn: Callable) -> Callable:
|
|
159
|
+
fn._is_builder_returns = True
|
|
160
|
+
fn._builder_child_class = child_class
|
|
161
|
+
return fn
|
|
162
|
+
return decorate
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def builder_terminal(fn: Callable) -> Callable:
|
|
166
|
+
"""Mark a method as the trace terminator.
|
|
167
|
+
|
|
168
|
+
A terminal handler:
|
|
169
|
+
|
|
170
|
+
* Emits synthesized declarations through ``ctx.emit_record`` /
|
|
171
|
+
``ctx.emit_function``,
|
|
172
|
+
* Rewrites the original call site via ``ctx.replace_call``,
|
|
173
|
+
* Returns the ``TypeInfo`` for the call's result type (so the LHS in
|
|
174
|
+
``x = builder.terminal(...)`` is statically typed).
|
|
175
|
+
"""
|
|
176
|
+
fn._is_builder_terminal = True
|
|
177
|
+
return fn
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
# Macro module dependency declaration
|
|
182
|
+
# ---------------------------------------------------------------------------
|
|
183
|
+
|
|
184
|
+
# Set by macro_deps(), read by MacroRegistry.load_module().
|
|
185
|
+
_pending_macro_deps: dict[str, list[str] | None] | None = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def macro_deps(*args: str | tuple[str, ...]) -> None:
|
|
189
|
+
"""Declare modules that macro-generated code depends on.
|
|
190
|
+
|
|
191
|
+
Called at module level in a macro module. The module name is bound
|
|
192
|
+
in the macro namespace so macro expansions can use qualified calls
|
|
193
|
+
(e.g. ``ast.method_call(ast.name("mod"), "func", args)``).
|
|
194
|
+
Individual function names are NOT injected into user scope.
|
|
195
|
+
|
|
196
|
+
Each argument is a module name string: ``"tplib.json.parser"``.
|
|
197
|
+
The tuple form ``("module", "name1", ...)`` is accepted for backward
|
|
198
|
+
compatibility but the name filter is ignored -- use qualified calls.
|
|
199
|
+
|
|
200
|
+
Example::
|
|
201
|
+
|
|
202
|
+
macro_deps("log_infra", "tpy.unsafe")
|
|
203
|
+
"""
|
|
204
|
+
global _pending_macro_deps
|
|
205
|
+
if _pending_macro_deps is not None:
|
|
206
|
+
raise MacroError("macro_deps() called twice in the same module")
|
|
207
|
+
deps: dict[str, list[str] | None] = {}
|
|
208
|
+
for arg in args:
|
|
209
|
+
if isinstance(arg, str):
|
|
210
|
+
deps[arg] = None
|
|
211
|
+
else:
|
|
212
|
+
module = arg[0]
|
|
213
|
+
names = list(arg[1:])
|
|
214
|
+
if module in deps and deps[module] is None:
|
|
215
|
+
pass # None (all exports) already set
|
|
216
|
+
elif module in deps and deps[module] is not None:
|
|
217
|
+
deps[module] = list(set(deps[module]) | set(names))
|
|
218
|
+
else:
|
|
219
|
+
deps[module] = names
|
|
220
|
+
_pending_macro_deps = deps
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ---------------------------------------------------------------------------
|
|
224
|
+
# MacroFStringPart -- typed expression from a decomposed f-string
|
|
225
|
+
# ---------------------------------------------------------------------------
|
|
226
|
+
|
|
227
|
+
@dataclass
|
|
228
|
+
class MacroFStringPart:
|
|
229
|
+
"""One expression part of a decomposed f-string.
|
|
230
|
+
|
|
231
|
+
Provides the original AST expression, its resolved type, and the
|
|
232
|
+
format spec (if any). Used by call macros to apply per-type wrapping
|
|
233
|
+
when building logging or serialization calls.
|
|
234
|
+
"""
|
|
235
|
+
expr: TpyExpr
|
|
236
|
+
type: TypeInfo
|
|
237
|
+
format_spec: str | None = None
|
|
238
|
+
conversion: int = -1 # FSTRING_CONV_NONE
|
|
239
|
+
is_static_str: bool = False
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def is_string_literal(self) -> bool:
|
|
243
|
+
"""True if the expression is a string literal (static storage in C++)."""
|
|
244
|
+
return isinstance(self.expr, TpyStrLiteral)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _is_static_str(expr: TpyExpr, ctx: 'SemanticContext | None' = None) -> bool:
|
|
248
|
+
"""Check if an expression resolves to static string storage at compile time.
|
|
249
|
+
|
|
250
|
+
Detects:
|
|
251
|
+
* direct string literals,
|
|
252
|
+
* ternary expressions whose branches are both static strings,
|
|
253
|
+
* name references to module-level ``Final[str]`` constants, including
|
|
254
|
+
those imported transitively from other modules (``from m import X``).
|
|
255
|
+
"""
|
|
256
|
+
if isinstance(expr, TpyStrLiteral):
|
|
257
|
+
return True
|
|
258
|
+
if isinstance(expr, TpyIfExpr):
|
|
259
|
+
return _is_static_str(expr.then_expr, ctx) and _is_static_str(expr.else_expr, ctx)
|
|
260
|
+
if isinstance(expr, TpyName) and ctx is not None:
|
|
261
|
+
return _name_is_final_str(expr.name, ctx)
|
|
262
|
+
return False
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _name_is_final_str(name: str, ctx: 'SemanticContext') -> bool:
|
|
266
|
+
"""True when ``name`` refers to a module-level Final[str] constant."""
|
|
267
|
+
# Local module declaration -- locally-declared bindings have
|
|
268
|
+
# `defining_module is None`, so `lookup_imported` returns None and
|
|
269
|
+
# we fall through to the import path below.
|
|
270
|
+
if name in ctx.final_globals:
|
|
271
|
+
local_type = ctx.global_scope.lookup(name)
|
|
272
|
+
return local_type is not None and _is_str_like(local_type)
|
|
273
|
+
# Imported from another module
|
|
274
|
+
source = lookup_imported(ctx.module_attributes, name, SymbolKind.VARIABLE)
|
|
275
|
+
if source is None:
|
|
276
|
+
return False
|
|
277
|
+
source_module, original_name = source
|
|
278
|
+
module_info = ctx.registry.get_module(source_module)
|
|
279
|
+
if module_info is None:
|
|
280
|
+
return False
|
|
281
|
+
var_info = module_info.variables.get(original_name)
|
|
282
|
+
if var_info is None:
|
|
283
|
+
return False
|
|
284
|
+
return var_info.is_final and _is_str_like(var_info.type)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _is_str_like(typ: TpyType) -> bool:
|
|
288
|
+
"""True for `str` / `StrView` / Final wrappers thereof."""
|
|
289
|
+
inner = unwrap_final(typ)
|
|
290
|
+
return is_str_type(inner) or is_str_view_type(inner)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
# MacroArg -- argument wrapper for call-site macros
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
@dataclass
|
|
298
|
+
class MacroArg:
|
|
299
|
+
"""Argument passed to a call-site macro: the AST expression + resolved type."""
|
|
300
|
+
expr: TpyExpr
|
|
301
|
+
type: TypeInfo
|
|
302
|
+
_fstring_parts: list[MacroFStringPart] | None = field(default=None, repr=False)
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def is_fstring(self) -> bool:
|
|
306
|
+
"""True if this argument is an f-string literal (not a plain string).
|
|
307
|
+
|
|
308
|
+
Note: as_fstring() also accepts plain string literals -- use it
|
|
309
|
+
directly when plain strings should be treated as static f-strings.
|
|
310
|
+
"""
|
|
311
|
+
return isinstance(self.expr, TpyFString)
|
|
312
|
+
|
|
313
|
+
def as_fstring(self) -> tuple[str, list[MacroFStringPart]] | None:
|
|
314
|
+
"""Decompose an f-string argument into format template + typed parts.
|
|
315
|
+
|
|
316
|
+
Returns (format_template, parts) where format_template has ``{}``
|
|
317
|
+
placeholders (with optional format specs like ``{:.2f}``) and parts
|
|
318
|
+
is a list of MacroFStringPart for each expression.
|
|
319
|
+
|
|
320
|
+
Also accepts plain string literals (treated as a static format
|
|
321
|
+
template with no expression parts).
|
|
322
|
+
|
|
323
|
+
Returns None if this argument is not an f-string or string literal.
|
|
324
|
+
"""
|
|
325
|
+
if isinstance(self.expr, TpyStrLiteral):
|
|
326
|
+
fmt = self.expr.value.replace("{", "{{").replace("}", "}}")
|
|
327
|
+
return fmt, []
|
|
328
|
+
if not isinstance(self.expr, TpyFString):
|
|
329
|
+
return None
|
|
330
|
+
assert self._fstring_parts is not None, \
|
|
331
|
+
"f-string parts not pre-computed (internal error)"
|
|
332
|
+
fmt_pieces: list[str] = []
|
|
333
|
+
for part in self.expr.parts:
|
|
334
|
+
if isinstance(part, str):
|
|
335
|
+
fmt_pieces.append(part.replace("{", "{{").replace("}", "}}"))
|
|
336
|
+
elif isinstance(part, TpyFStringValue):
|
|
337
|
+
spec = part.format_spec
|
|
338
|
+
if spec is not None:
|
|
339
|
+
fmt_pieces.append("{:" + spec + "}")
|
|
340
|
+
else:
|
|
341
|
+
fmt_pieces.append("{}")
|
|
342
|
+
return "".join(fmt_pieces), list(self._fstring_parts)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ---------------------------------------------------------------------------
|
|
346
|
+
# CallMacroContext -- context for call-site macros
|
|
347
|
+
# ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
class CallMacroContext:
|
|
350
|
+
"""Context passed to call-site macros for type introspection and diagnostics."""
|
|
351
|
+
|
|
352
|
+
def __init__(self, ctx: SemanticContext, loc: Any = None) -> None:
|
|
353
|
+
self._ctx = ctx
|
|
354
|
+
self._loc = loc
|
|
355
|
+
|
|
356
|
+
def get_record_fields(self, name: str) -> list[FieldInfo] | None:
|
|
357
|
+
"""Get all fields (parent + own) for a macro record, or None.
|
|
358
|
+
|
|
359
|
+
Only returns fields for records that called set_match_args().
|
|
360
|
+
"""
|
|
361
|
+
record_info = self._ctx.registry.get_record(name)
|
|
362
|
+
if record_info is None or record_info.match_args is None:
|
|
363
|
+
return None
|
|
364
|
+
all_fields = self._ctx.registry.get_all_fields(record_info)
|
|
365
|
+
return [FieldInfo.from_internal(f) for f in all_fields]
|
|
366
|
+
|
|
367
|
+
def get_iterable_element_type(self, type_info: TypeInfo) -> TypeInfo | None:
|
|
368
|
+
"""Get the element type of an iterable type, or None if not iterable.
|
|
369
|
+
|
|
370
|
+
Handles built-in containers, user types with __iter__, and protocols.
|
|
371
|
+
"""
|
|
372
|
+
if type_info._tpy_type is None:
|
|
373
|
+
return None
|
|
374
|
+
from .sema.list_literals import IterableHelper
|
|
375
|
+
helper = IterableHelper(self._ctx)
|
|
376
|
+
elem = helper.get_iterable_element_type_or_none(type_info._tpy_type)
|
|
377
|
+
if elem is not None:
|
|
378
|
+
return TypeInfo.from_tpy_type(elem)
|
|
379
|
+
return None
|
|
380
|
+
|
|
381
|
+
@property
|
|
382
|
+
def in_method(self) -> bool:
|
|
383
|
+
"""True if the call site is inside a method body (self is available)."""
|
|
384
|
+
record = self._ctx.record_ctx.record
|
|
385
|
+
if record is None:
|
|
386
|
+
return False
|
|
387
|
+
func = self._ctx.func.current_function
|
|
388
|
+
return isinstance(func, TpyFunction) and func.is_method
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def self_type(self) -> TypeInfo | None:
|
|
392
|
+
"""The current class as TypeInfo, or None if not in a method."""
|
|
393
|
+
record = self._ctx.record_ctx.record
|
|
394
|
+
if record is None:
|
|
395
|
+
return None
|
|
396
|
+
func = self._ctx.func.current_function
|
|
397
|
+
if not isinstance(func, TpyFunction) or not func.is_method:
|
|
398
|
+
return None
|
|
399
|
+
return TypeInfo.from_tpy_type(NominalType(record.name))
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def first_param(self) -> tuple[str, TypeInfo] | None:
|
|
403
|
+
"""First parameter of the current function (self for methods), or None.
|
|
404
|
+
|
|
405
|
+
For methods, returns ("self", <class TypeInfo>).
|
|
406
|
+
For free functions, returns the first declared parameter.
|
|
407
|
+
"""
|
|
408
|
+
func = self._ctx.func.current_function
|
|
409
|
+
if not isinstance(func, TpyFunction):
|
|
410
|
+
return None
|
|
411
|
+
if func.is_method and not func.is_staticmethod:
|
|
412
|
+
st = self.self_type
|
|
413
|
+
if st is not None:
|
|
414
|
+
return ("self", st)
|
|
415
|
+
return None
|
|
416
|
+
if not func.params:
|
|
417
|
+
return None
|
|
418
|
+
name, tpy_type = func.params[0]
|
|
419
|
+
return (name, TypeInfo.from_tpy_type(tpy_type))
|
|
420
|
+
|
|
421
|
+
def self_field(self, name: str) -> TpyExpr:
|
|
422
|
+
"""Return AST for ``self.<name>``. Only valid when in_method is True."""
|
|
423
|
+
if not self.in_method:
|
|
424
|
+
self.error("self_field() called outside a method context")
|
|
425
|
+
return TpyFieldAccess(obj=TpyName("self"), field=name)
|
|
426
|
+
|
|
427
|
+
def _get_record_info(self, type_info: TypeInfo) -> Any:
|
|
428
|
+
"""Look up RecordInfo for a TypeInfo, or None."""
|
|
429
|
+
if type_info._tpy_type is None:
|
|
430
|
+
return None
|
|
431
|
+
return self._ctx.registry.get_record(type_info.name)
|
|
432
|
+
|
|
433
|
+
def get_field_type(self, type_info: TypeInfo, name: str) -> TypeInfo | None:
|
|
434
|
+
"""Get the type of a named field on a record (including inherited), or None."""
|
|
435
|
+
record_info = self._get_record_info(type_info)
|
|
436
|
+
if record_info is None:
|
|
437
|
+
return None
|
|
438
|
+
for f in self._ctx.registry.get_all_fields(record_info):
|
|
439
|
+
if f.name == name:
|
|
440
|
+
return TypeInfo.from_tpy_type(f.type)
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
def get_method_return_type(self, type_info: TypeInfo, name: str) -> TypeInfo | None:
|
|
444
|
+
"""Get the return type of a named method on a record (including inherited), or None."""
|
|
445
|
+
record_info = self._get_record_info(type_info)
|
|
446
|
+
if record_info is None:
|
|
447
|
+
return None
|
|
448
|
+
overloads = self._ctx.registry.get_method_overloads_with_parents(record_info, name)
|
|
449
|
+
if not overloads:
|
|
450
|
+
return None
|
|
451
|
+
return TypeInfo.from_tpy_type(overloads[0].return_type)
|
|
452
|
+
|
|
453
|
+
def qualified_name(self, type_info: TypeInfo) -> str:
|
|
454
|
+
"""Module-qualified type name (e.g. 'log_infra.LogHandle').
|
|
455
|
+
|
|
456
|
+
For builtin/module types, uses the type's own qualified name.
|
|
457
|
+
For user records, searches the registry modules.
|
|
458
|
+
Falls back to the short name if the module is unknown.
|
|
459
|
+
"""
|
|
460
|
+
tpy_type = type_info._tpy_type
|
|
461
|
+
if isinstance(tpy_type, NominalType):
|
|
462
|
+
qn = tpy_type.qualified_name()
|
|
463
|
+
if qn is not None:
|
|
464
|
+
return qn
|
|
465
|
+
# Search registry modules for user-defined records
|
|
466
|
+
for mod_name, mod_info in self._ctx.registry.modules.items():
|
|
467
|
+
if type_info.name in mod_info.records:
|
|
468
|
+
return f"{mod_name}.{type_info.name}"
|
|
469
|
+
return type_info.name
|
|
470
|
+
|
|
471
|
+
def warning(self, msg: str, loc: Any = None) -> None:
|
|
472
|
+
"""Emit a compiler warning."""
|
|
473
|
+
self._ctx.warning_from_loc(msg, loc or self._loc)
|
|
474
|
+
|
|
475
|
+
def error(self, msg: str, loc: Any = None) -> NoReturn:
|
|
476
|
+
"""Raise a compile error."""
|
|
477
|
+
from .diagnostics import SemanticError
|
|
478
|
+
raise SemanticError(msg, loc or self._loc)
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
# ---------------------------------------------------------------------------
|
|
482
|
+
# MacroArgs -- positional + keyword args delivered to builder handlers
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
|
|
485
|
+
@dataclass
|
|
486
|
+
class MacroArgs:
|
|
487
|
+
"""Positional and keyword arguments passed to a builder handler.
|
|
488
|
+
|
|
489
|
+
Each value is a ``MacroArg`` (raw expression + resolved type).
|
|
490
|
+
Use the ``BuilderContext`` extractors (``positional_str``,
|
|
491
|
+
``kwarg_int``, ...) to pull macro-time values out of these.
|
|
492
|
+
"""
|
|
493
|
+
positional: list[MacroArg] = field(default_factory=list)
|
|
494
|
+
kwargs: dict[str, MacroArg] = field(default_factory=dict)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
# ---------------------------------------------------------------------------
|
|
498
|
+
# BuilderContext -- API surface for @builder_macro handlers
|
|
499
|
+
# ---------------------------------------------------------------------------
|
|
500
|
+
|
|
501
|
+
# Sentinel: distinguishes "kwarg absent, use default" from "kwarg
|
|
502
|
+
# explicitly set to None". Macro authors never see this -- it stays
|
|
503
|
+
# inside the BuilderContext extractors.
|
|
504
|
+
_UNSET = object()
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class BuilderContext:
|
|
508
|
+
"""Context passed to @builder_macro handlers.
|
|
509
|
+
|
|
510
|
+
Provides:
|
|
511
|
+
|
|
512
|
+
* **Diagnostics**: ``warning(msg, loc=None)`` / ``error(msg, loc=None)``.
|
|
513
|
+
* **Macro-time literal evaluators**: ``eval_literal_or_final(expr)`` and
|
|
514
|
+
``eval_sequence_of_literal_or_final(expr)`` -- the primitives over
|
|
515
|
+
which all kwarg extractors are built.
|
|
516
|
+
* **Typed extractors**: ``positional_str(args, i)``, ``kwarg_str``,
|
|
517
|
+
``kwarg_int``, ``kwarg_bool``, ``kwarg_str_or_int``, ``kwarg_type``,
|
|
518
|
+
``kwarg_list_literal``, ``kwarg_macroarg``.
|
|
519
|
+
* **Code emission**: ``fresh_module_name(hint)``, ``emit_record``,
|
|
520
|
+
``emit_function``, ``replace_call``.
|
|
521
|
+
|
|
522
|
+
Construction is internal -- the BuilderTraceExpander wires up
|
|
523
|
+
``_expander``, ``_ctx``, ``_call_loc``, and ``_function_being_traced``.
|
|
524
|
+
|
|
525
|
+
The code-emission methods are intentionally macro-kind-agnostic --
|
|
526
|
+
they only depend on the module, the registrar, and the per-module
|
|
527
|
+
fresh-name counter on SemanticContext. When a class-macro or
|
|
528
|
+
call-macro consumer needs to emit sibling records or top-level
|
|
529
|
+
helper functions, lift these methods into a shared ``ModuleEmitter``
|
|
530
|
+
helper that ``ClassInfo`` and ``CallMacroContext`` can hold a
|
|
531
|
+
reference to. Tracked as "Top-level companion records / functions"
|
|
532
|
+
in docs/MACRO_DESIGN.md's Macro System Future Work table.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
def __init__(
|
|
536
|
+
self, expander: Any, ctx: 'SemanticContext',
|
|
537
|
+
call_loc: Any, function_being_traced: str,
|
|
538
|
+
) -> None:
|
|
539
|
+
self._expander = expander
|
|
540
|
+
self._ctx = ctx
|
|
541
|
+
self._call_loc = call_loc
|
|
542
|
+
self._function_being_traced = function_being_traced
|
|
543
|
+
|
|
544
|
+
# -- Diagnostics --
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def call_loc(self) -> Any:
|
|
548
|
+
"""Source location of the call currently being expanded."""
|
|
549
|
+
return self._call_loc
|
|
550
|
+
|
|
551
|
+
@property
|
|
552
|
+
def function_being_traced(self) -> str:
|
|
553
|
+
"""Name of the enclosing function whose body is being traced."""
|
|
554
|
+
return self._function_being_traced
|
|
555
|
+
|
|
556
|
+
def warning(self, msg: str, loc: Any = None) -> None:
|
|
557
|
+
"""Emit a compiler warning at the current call site (or `loc`)."""
|
|
558
|
+
self._ctx.warning_from_loc(msg, loc or self._call_loc)
|
|
559
|
+
|
|
560
|
+
def error(self, msg: str, loc: Any = None) -> NoReturn:
|
|
561
|
+
"""Raise a compile error at the current call site (or `loc`)."""
|
|
562
|
+
from .diagnostics import SemanticError
|
|
563
|
+
raise SemanticError(msg, loc or self._call_loc)
|
|
564
|
+
|
|
565
|
+
# -- Macro-time literal evaluators (the primitives) --
|
|
566
|
+
|
|
567
|
+
def eval_literal_or_final(self, expr: TpyExpr) -> object:
|
|
568
|
+
"""Evaluate an expression to a Python value at macro time.
|
|
569
|
+
|
|
570
|
+
Accepts int/str/float/bool/None literals, unary minus on
|
|
571
|
+
numeric literals, and tuple/list literals whose elements are
|
|
572
|
+
themselves ``eval_literal_or_final``-compatible (returns a
|
|
573
|
+
Python ``list`` in that case -- nested lists work the same
|
|
574
|
+
way). The name is forward-looking: ``Final[...]`` constant
|
|
575
|
+
references will resolve here once the lookup-by-name path is
|
|
576
|
+
wired (today the resolver returns _UNSET for any name, so
|
|
577
|
+
only literals work). Raises ``MacroError`` if the expression
|
|
578
|
+
is not statically knowable.
|
|
579
|
+
"""
|
|
580
|
+
return _eval_literal_or_final(expr, self._ctx, self._call_loc)
|
|
581
|
+
|
|
582
|
+
def eval_sequence_of_literal_or_final(self, expr: TpyExpr) -> list[object]:
|
|
583
|
+
"""Evaluate a tuple/list literal whose elements are all
|
|
584
|
+
``eval_literal_or_final``-compatible.
|
|
585
|
+
"""
|
|
586
|
+
if isinstance(expr, (TpyTupleLiteral, TpyArrayLiteral)):
|
|
587
|
+
return [self.eval_literal_or_final(e) for e in expr.elements]
|
|
588
|
+
raise MacroError(
|
|
589
|
+
f"expected a tuple or list literal, got {type(expr).__name__}",
|
|
590
|
+
loc=self._call_loc,
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
# -- Typed positional extractors --
|
|
594
|
+
|
|
595
|
+
def positional_strs(self, args: MacroArgs) -> list[str]:
|
|
596
|
+
"""Extract every positional arg as a string literal value.
|
|
597
|
+
|
|
598
|
+
Errors if any positional is not a string literal.
|
|
599
|
+
"""
|
|
600
|
+
out: list[str] = []
|
|
601
|
+
for i, ma in enumerate(args.positional):
|
|
602
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
603
|
+
if not isinstance(v, str):
|
|
604
|
+
raise MacroError(
|
|
605
|
+
f"positional argument #{i} must be a string, got {type(v).__name__}",
|
|
606
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
607
|
+
)
|
|
608
|
+
out.append(v)
|
|
609
|
+
return out
|
|
610
|
+
|
|
611
|
+
def positional_str(self, args: MacroArgs, i: int) -> str:
|
|
612
|
+
if i >= len(args.positional):
|
|
613
|
+
raise MacroError(
|
|
614
|
+
f"missing positional argument #{i}", loc=self._call_loc)
|
|
615
|
+
ma = args.positional[i]
|
|
616
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
617
|
+
if not isinstance(v, str):
|
|
618
|
+
raise MacroError(
|
|
619
|
+
f"positional argument #{i} must be a string, got {type(v).__name__}",
|
|
620
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
621
|
+
)
|
|
622
|
+
return v
|
|
623
|
+
|
|
624
|
+
# -- Typed kwarg extractors --
|
|
625
|
+
|
|
626
|
+
def _get_kwarg(self, args: MacroArgs, name: str) -> MacroArg | None:
|
|
627
|
+
return args.kwargs.get(name)
|
|
628
|
+
|
|
629
|
+
def kwarg_str(
|
|
630
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
631
|
+
) -> str | None:
|
|
632
|
+
ma = self._get_kwarg(args, name)
|
|
633
|
+
if ma is None:
|
|
634
|
+
return None if default is _UNSET else default
|
|
635
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
636
|
+
if v is None:
|
|
637
|
+
return None
|
|
638
|
+
if not isinstance(v, str):
|
|
639
|
+
raise MacroError(
|
|
640
|
+
f"keyword argument '{name}' must be a string, got {type(v).__name__}",
|
|
641
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
642
|
+
)
|
|
643
|
+
return v
|
|
644
|
+
|
|
645
|
+
def kwarg_int(
|
|
646
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
647
|
+
) -> int | None:
|
|
648
|
+
ma = self._get_kwarg(args, name)
|
|
649
|
+
if ma is None:
|
|
650
|
+
return None if default is _UNSET else default
|
|
651
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
652
|
+
if v is None:
|
|
653
|
+
return None
|
|
654
|
+
if not isinstance(v, int) or isinstance(v, bool):
|
|
655
|
+
raise MacroError(
|
|
656
|
+
f"keyword argument '{name}' must be an int, got {type(v).__name__}",
|
|
657
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
658
|
+
)
|
|
659
|
+
return v
|
|
660
|
+
|
|
661
|
+
def kwarg_bool(
|
|
662
|
+
self, args: MacroArgs, name: str, default: bool = False,
|
|
663
|
+
) -> bool:
|
|
664
|
+
ma = self._get_kwarg(args, name)
|
|
665
|
+
if ma is None:
|
|
666
|
+
return default
|
|
667
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
668
|
+
if not isinstance(v, bool):
|
|
669
|
+
raise MacroError(
|
|
670
|
+
f"keyword argument '{name}' must be a bool, got {type(v).__name__}",
|
|
671
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
672
|
+
)
|
|
673
|
+
return v
|
|
674
|
+
|
|
675
|
+
def kwarg_str_or_int(
|
|
676
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
677
|
+
) -> Any:
|
|
678
|
+
ma = self._get_kwarg(args, name)
|
|
679
|
+
if ma is None:
|
|
680
|
+
return None if default is _UNSET else default
|
|
681
|
+
v = self.eval_literal_or_final(ma.expr)
|
|
682
|
+
if v is None:
|
|
683
|
+
return None
|
|
684
|
+
if isinstance(v, bool) or not isinstance(v, (str, int)):
|
|
685
|
+
raise MacroError(
|
|
686
|
+
f"keyword argument '{name}' must be a string or int, got {type(v).__name__}",
|
|
687
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
688
|
+
)
|
|
689
|
+
return v
|
|
690
|
+
|
|
691
|
+
def kwarg_type(
|
|
692
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
693
|
+
) -> TypeInfo | None:
|
|
694
|
+
"""Extract a kwarg whose value is a type reference (e.g. ``type=int``)."""
|
|
695
|
+
ma = self._get_kwarg(args, name)
|
|
696
|
+
if ma is None:
|
|
697
|
+
return None if default is _UNSET else default
|
|
698
|
+
return ma.type
|
|
699
|
+
|
|
700
|
+
def kwarg_list_literal(
|
|
701
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
702
|
+
) -> list[MacroArg] | None:
|
|
703
|
+
"""Extract a kwarg that must be a tuple or list literal.
|
|
704
|
+
|
|
705
|
+
Returns the element ``MacroArg``s wrapped in MacroArg form so the
|
|
706
|
+
caller can inspect each element's type and expression.
|
|
707
|
+
"""
|
|
708
|
+
ma = self._get_kwarg(args, name)
|
|
709
|
+
if ma is None:
|
|
710
|
+
return None if default is _UNSET else default
|
|
711
|
+
if not isinstance(ma.expr, (TpyTupleLiteral, TpyArrayLiteral)):
|
|
712
|
+
raise MacroError(
|
|
713
|
+
f"keyword argument '{name}' must be a tuple or list literal",
|
|
714
|
+
loc=getattr(ma.expr, "loc", None) or self._call_loc,
|
|
715
|
+
)
|
|
716
|
+
# Wrap each element with an unknown type -- callers that need
|
|
717
|
+
# concrete element types should use eval_sequence_of_literal_or_final
|
|
718
|
+
# or apply their own per-element extractors.
|
|
719
|
+
return [MacroArg(expr=e, type=TypeInfo("?", _tpy_type=None))
|
|
720
|
+
for e in ma.expr.elements]
|
|
721
|
+
|
|
722
|
+
def kwarg_macroarg(
|
|
723
|
+
self, args: MacroArgs, name: str, default: Any = _UNSET,
|
|
724
|
+
) -> MacroArg | None:
|
|
725
|
+
"""Return the raw MacroArg for a kwarg, or ``default`` if absent."""
|
|
726
|
+
ma = self._get_kwarg(args, name)
|
|
727
|
+
if ma is None:
|
|
728
|
+
return None if default is _UNSET else default
|
|
729
|
+
return ma
|
|
730
|
+
|
|
731
|
+
# -- Type introspection --
|
|
732
|
+
|
|
733
|
+
def get_static_method_return_type(
|
|
734
|
+
self, type_info: TypeInfo, name: str,
|
|
735
|
+
) -> "TypeInfo | None":
|
|
736
|
+
"""Return type of a ``@staticmethod`` named ``name`` on ``type_info``,
|
|
737
|
+
or ``None`` when the method is absent or non-static.
|
|
738
|
+
|
|
739
|
+
One call answers "is this type usable as a structurally-typed
|
|
740
|
+
factory" -- macros use it to detect protocol-like hooks (e.g.
|
|
741
|
+
argparse's ``ArgType`` ``from_arg`` factory) without needing
|
|
742
|
+
the type to inherit from a formal Protocol.
|
|
743
|
+
|
|
744
|
+
Looked up by name -- builder-trace passes user-defined types
|
|
745
|
+
with ``_tpy_type=None`` (full type-name resolution in builder
|
|
746
|
+
contexts is still pending), so ``type_info.name`` is the only
|
|
747
|
+
reliable handle.
|
|
748
|
+
"""
|
|
749
|
+
record_info = self._ctx.registry.get_record(type_info.name)
|
|
750
|
+
if record_info is None:
|
|
751
|
+
return None
|
|
752
|
+
overloads = self._ctx.registry.get_method_overloads_with_parents(
|
|
753
|
+
record_info, name)
|
|
754
|
+
if not overloads or not overloads[0].is_staticmethod:
|
|
755
|
+
return None
|
|
756
|
+
return TypeInfo.from_tpy_type(overloads[0].return_type)
|
|
757
|
+
|
|
758
|
+
def resolve_type_info(self, type_info: TypeInfo) -> TypeInfo:
|
|
759
|
+
"""Materialize a builder-trace user-type placeholder into a
|
|
760
|
+
qname-bearing ``TypeInfo``, or return the input unchanged.
|
|
761
|
+
|
|
762
|
+
Builder-trace receives user-defined ``type=`` references with
|
|
763
|
+
``_tpy_type=None`` (full type-name resolution at the builder
|
|
764
|
+
layer is still pending). Macros that emit field types or
|
|
765
|
+
record fields backed by such a placeholder need the resolved,
|
|
766
|
+
qname-bearing ``NominalType`` so identity matches between the
|
|
767
|
+
emitted-record's field and sema-resolved references in the
|
|
768
|
+
synthesized function body. Built-in types that already carry a
|
|
769
|
+
``_tpy_type`` are returned unchanged.
|
|
770
|
+
"""
|
|
771
|
+
if type_info._tpy_type is not None:
|
|
772
|
+
return type_info
|
|
773
|
+
record_info = self._ctx.registry.get_record(type_info.name)
|
|
774
|
+
if record_info is None:
|
|
775
|
+
return type_info
|
|
776
|
+
qname = record_info.qualified_name()
|
|
777
|
+
if not qname:
|
|
778
|
+
return type_info
|
|
779
|
+
return TypeInfo.from_tpy_type(
|
|
780
|
+
NominalType(type_info.name, _module_qname=qname)
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
# -- Code emission --
|
|
784
|
+
|
|
785
|
+
def fresh_module_name(self, hint: str) -> str:
|
|
786
|
+
"""Synthesize a unique module-private name for a generated record
|
|
787
|
+
or function. Names start with an underscore so they aren't exported.
|
|
788
|
+
"""
|
|
789
|
+
return self._expander.fresh_module_name(hint)
|
|
790
|
+
|
|
791
|
+
def emit_record(
|
|
792
|
+
self, name: str, fields: list[tuple[str, TpyType]],
|
|
793
|
+
methods: list[Function] | None = None,
|
|
794
|
+
) -> TypeInfo:
|
|
795
|
+
"""Synthesize and register a record type.
|
|
796
|
+
|
|
797
|
+
``fields`` is a list of ``(name, type)`` pairs. Returns the
|
|
798
|
+
``TypeInfo`` for the new type so handlers can use it as the return
|
|
799
|
+
type of an emitted function.
|
|
800
|
+
"""
|
|
801
|
+
return self._expander.emit_record(name, fields, methods or [],
|
|
802
|
+
loc=self._call_loc)
|
|
803
|
+
|
|
804
|
+
def emit_function(
|
|
805
|
+
self, name: str, params: list[tuple[str, TpyType]],
|
|
806
|
+
return_type: TpyType, body: list[Stmt],
|
|
807
|
+
) -> str:
|
|
808
|
+
"""Synthesize and register a free function. Returns its module-
|
|
809
|
+
private name (which is also what ``replace_call`` expects).
|
|
810
|
+
"""
|
|
811
|
+
return self._expander.emit_function(name, params, return_type, body,
|
|
812
|
+
loc=self._call_loc)
|
|
813
|
+
|
|
814
|
+
def replace_call(self, fn_name: str, args: MacroArgs) -> None:
|
|
815
|
+
"""Rewrite the trace's terminal call site to ``fn_name(<args>)``.
|
|
816
|
+
|
|
817
|
+
Positional and keyword args from ``args`` are passed through
|
|
818
|
+
unchanged.
|
|
819
|
+
"""
|
|
820
|
+
self._expander.replace_call(fn_name, args, loc=self._call_loc)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _eval_literal_or_final(
|
|
824
|
+
expr: TpyExpr, ctx: 'SemanticContext', loc: Any,
|
|
825
|
+
) -> object:
|
|
826
|
+
"""Implementation of BuilderContext.eval_literal_or_final."""
|
|
827
|
+
if isinstance(expr, TpyBoolLiteral):
|
|
828
|
+
return expr.value
|
|
829
|
+
if isinstance(expr, TpyIntLiteral):
|
|
830
|
+
return expr.value
|
|
831
|
+
if isinstance(expr, TpyFloatLiteral):
|
|
832
|
+
return expr.value
|
|
833
|
+
if isinstance(expr, TpyStrLiteral):
|
|
834
|
+
return expr.value
|
|
835
|
+
if isinstance(expr, TpyNoneLiteral):
|
|
836
|
+
return None
|
|
837
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "-":
|
|
838
|
+
inner = _eval_literal_or_final(expr.operand, ctx, loc)
|
|
839
|
+
if isinstance(inner, bool) or not isinstance(inner, (int, float)):
|
|
840
|
+
raise MacroError(
|
|
841
|
+
f"unary minus requires a numeric literal, got {type(inner).__name__}",
|
|
842
|
+
loc=getattr(expr, "loc", None) or loc,
|
|
843
|
+
)
|
|
844
|
+
return -inner
|
|
845
|
+
if isinstance(expr, (TpyTupleLiteral, TpyArrayLiteral)):
|
|
846
|
+
return [_eval_literal_or_final(e, ctx, loc) for e in expr.elements]
|
|
847
|
+
if isinstance(expr, TpyName):
|
|
848
|
+
# Final[...] global with a literal initializer -- look it up.
|
|
849
|
+
v = _resolve_final_literal(expr.name, ctx)
|
|
850
|
+
if v is not _UNSET:
|
|
851
|
+
return v
|
|
852
|
+
raise MacroError(
|
|
853
|
+
f"expected a literal or Final constant, got {type(expr).__name__}",
|
|
854
|
+
loc=getattr(expr, "loc", None) or loc,
|
|
855
|
+
)
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def _resolve_final_literal(name: str, ctx: 'SemanticContext') -> object:
|
|
859
|
+
"""Resolve a name to the Python value of a Final[...] literal global,
|
|
860
|
+
or return _UNSET if not resolvable.
|
|
861
|
+
|
|
862
|
+
Final-literal evaluation requires walking the module's top-level
|
|
863
|
+
decls to find the initializer expression. v1 doesn't yet thread
|
|
864
|
+
that through to BuilderContext, so this always returns _UNSET --
|
|
865
|
+
builder kwargs must currently be literal expressions, not Final
|
|
866
|
+
references. Add init-expr lookup here when the use case appears.
|
|
867
|
+
"""
|
|
868
|
+
return _UNSET
|
|
869
|
+
|
|
870
|
+
|
|
871
|
+
# ---------------------------------------------------------------------------
|
|
872
|
+
# TypeInfo -- read-only wrapper around TpyType
|
|
873
|
+
# ---------------------------------------------------------------------------
|
|
874
|
+
|
|
875
|
+
@dataclass
|
|
876
|
+
class TypeInfo:
|
|
877
|
+
"""Read-only type metadata exposed to macros."""
|
|
878
|
+
name: str
|
|
879
|
+
type_args: list[TypeInfo] = field(default_factory=list)
|
|
880
|
+
is_optional: bool = False
|
|
881
|
+
is_value_type: bool = True
|
|
882
|
+
is_record: bool = False
|
|
883
|
+
_tpy_type: TpyType | None = None
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
@property
|
|
887
|
+
def raw_type(self) -> TpyType:
|
|
888
|
+
"""Access the underlying compiler type object (escape hatch)."""
|
|
889
|
+
assert self._tpy_type is not None, "raw_type on TypeInfo with no underlying type"
|
|
890
|
+
return self._tpy_type
|
|
891
|
+
|
|
892
|
+
@property
|
|
893
|
+
def is_str(self) -> bool:
|
|
894
|
+
"""True if this is any string type (str, String, StrView)."""
|
|
895
|
+
from .typesys import is_any_str_type
|
|
896
|
+
return self._tpy_type is not None and is_any_str_type(self._tpy_type)
|
|
897
|
+
|
|
898
|
+
@property
|
|
899
|
+
def is_int(self) -> bool:
|
|
900
|
+
from .type_def_registry import is_fixed_int_type
|
|
901
|
+
return is_fixed_int_type(self._tpy_type)
|
|
902
|
+
|
|
903
|
+
@property
|
|
904
|
+
def is_int32(self) -> bool:
|
|
905
|
+
from .type_def_registry import int_traits_of
|
|
906
|
+
tr = int_traits_of(self._tpy_type)
|
|
907
|
+
return tr is not None and tr.bits == 32 and tr.signed
|
|
908
|
+
|
|
909
|
+
@property
|
|
910
|
+
def is_float(self) -> bool:
|
|
911
|
+
return is_float_type(self._tpy_type)
|
|
912
|
+
|
|
913
|
+
@property
|
|
914
|
+
def is_float32(self) -> bool:
|
|
915
|
+
from .type_def_registry import is_float32_type
|
|
916
|
+
return is_float32_type(self._tpy_type)
|
|
917
|
+
|
|
918
|
+
@property
|
|
919
|
+
def is_bool(self) -> bool:
|
|
920
|
+
from .type_def_registry import is_bool_type
|
|
921
|
+
return is_bool_type(self._tpy_type)
|
|
922
|
+
|
|
923
|
+
@property
|
|
924
|
+
def is_bigint(self) -> bool:
|
|
925
|
+
from .type_def_registry import is_big_int_type
|
|
926
|
+
return is_big_int_type(self._tpy_type)
|
|
927
|
+
|
|
928
|
+
@property
|
|
929
|
+
def is_enum(self) -> bool:
|
|
930
|
+
from .type_def_registry import is_enum_type
|
|
931
|
+
return is_enum_type(self._tpy_type)
|
|
932
|
+
|
|
933
|
+
@property
|
|
934
|
+
def is_list(self) -> bool:
|
|
935
|
+
from .type_def_registry import is_list as _is_list
|
|
936
|
+
return _is_list(self._tpy_type)
|
|
937
|
+
|
|
938
|
+
@property
|
|
939
|
+
def is_dict(self) -> bool:
|
|
940
|
+
from .type_def_registry import is_dict as _is_dict
|
|
941
|
+
return _is_dict(self._tpy_type)
|
|
942
|
+
|
|
943
|
+
@property
|
|
944
|
+
def is_set(self) -> bool:
|
|
945
|
+
from .type_def_registry import is_set as _is_set
|
|
946
|
+
return _is_set(self._tpy_type)
|
|
947
|
+
|
|
948
|
+
@property
|
|
949
|
+
def is_tuple(self) -> bool:
|
|
950
|
+
return isinstance(self._tpy_type, TupleType)
|
|
951
|
+
|
|
952
|
+
@property
|
|
953
|
+
def enum_name(self) -> str:
|
|
954
|
+
"""Get the enum class name (only valid when is_enum is True)."""
|
|
955
|
+
from .type_def_registry import is_enum_type
|
|
956
|
+
assert is_enum_type(self._tpy_type)
|
|
957
|
+
return self._tpy_type.name
|
|
958
|
+
|
|
959
|
+
@property
|
|
960
|
+
def int_type_name(self) -> str:
|
|
961
|
+
"""Get the fixed-int type name e.g. 'Int32' (only valid when is_int)."""
|
|
962
|
+
from .type_def_registry import is_fixed_int_type
|
|
963
|
+
assert is_fixed_int_type(self._tpy_type)
|
|
964
|
+
return str(self._tpy_type)
|
|
965
|
+
|
|
966
|
+
def unwrap_optional(self) -> TypeInfo | None:
|
|
967
|
+
"""If this is Optional[T], return TypeInfo for T. Otherwise None."""
|
|
968
|
+
if self.is_optional and isinstance(self._tpy_type, OptionalType):
|
|
969
|
+
return TypeInfo.from_tpy_type(self._tpy_type.inner)
|
|
970
|
+
return None
|
|
971
|
+
|
|
972
|
+
@property
|
|
973
|
+
def tuple_element_types(self) -> list[TypeInfo]:
|
|
974
|
+
"""Get element types of a tuple (only valid when is_tuple)."""
|
|
975
|
+
assert isinstance(self._tpy_type, TupleType)
|
|
976
|
+
return [TypeInfo.from_tpy_type(et) for et in self._tpy_type.element_types]
|
|
977
|
+
|
|
978
|
+
@staticmethod
|
|
979
|
+
def from_tpy_type(typ: TpyType) -> TypeInfo:
|
|
980
|
+
from .typesys import RefType
|
|
981
|
+
# Strip sema-internal wrappers -- macros see user-facing types
|
|
982
|
+
if isinstance(typ, OwnType):
|
|
983
|
+
typ = typ.wrapped
|
|
984
|
+
if isinstance(typ, RefType):
|
|
985
|
+
typ = typ.wrapped
|
|
986
|
+
type_args: list[TypeInfo] = []
|
|
987
|
+
if isinstance(typ, TupleType):
|
|
988
|
+
type_args = [TypeInfo.from_tpy_type(et) for et in typ.element_types]
|
|
989
|
+
elif isinstance(typ, NominalType) and typ.type_args:
|
|
990
|
+
type_args = [TypeInfo.from_tpy_type(ta) for ta in typ.type_args]
|
|
991
|
+
return TypeInfo(
|
|
992
|
+
name=str(typ),
|
|
993
|
+
type_args=type_args,
|
|
994
|
+
is_optional=isinstance(typ, OptionalType),
|
|
995
|
+
is_value_type=typ.is_value_type(),
|
|
996
|
+
is_record=isinstance(typ, NominalType) and typ.is_user_record,
|
|
997
|
+
_tpy_type=typ,
|
|
998
|
+
)
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
# ---------------------------------------------------------------------------
|
|
1002
|
+
# FieldInfo -- read-only wrapper around typesys.FieldInfo
|
|
1003
|
+
# ---------------------------------------------------------------------------
|
|
1004
|
+
|
|
1005
|
+
@dataclass
|
|
1006
|
+
class FieldInfo:
|
|
1007
|
+
"""Field metadata exposed to macros."""
|
|
1008
|
+
name: str
|
|
1009
|
+
type: TypeInfo
|
|
1010
|
+
has_default: bool
|
|
1011
|
+
default_expr: TpyExpr | None = None
|
|
1012
|
+
is_factory_default: bool = False
|
|
1013
|
+
loc: Any = None
|
|
1014
|
+
default_obj: Any = None
|
|
1015
|
+
_internal: InternalFieldInfo | None = None
|
|
1016
|
+
|
|
1017
|
+
@staticmethod
|
|
1018
|
+
def from_internal(fld: InternalFieldInfo) -> FieldInfo:
|
|
1019
|
+
return FieldInfo(
|
|
1020
|
+
name=fld.name,
|
|
1021
|
+
type=TypeInfo.from_tpy_type(fld.type),
|
|
1022
|
+
has_default=fld.default_expr is not None,
|
|
1023
|
+
default_expr=fld.default_expr,
|
|
1024
|
+
is_factory_default=fld.is_factory_default,
|
|
1025
|
+
loc=fld.loc,
|
|
1026
|
+
_internal=fld,
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
def set_default(self, expr: TpyExpr | int | float | str | bool | None,
|
|
1030
|
+
default_value: str | None = None,
|
|
1031
|
+
is_factory: bool = False) -> None:
|
|
1032
|
+
"""Replace this field's default expression (updates the underlying record).
|
|
1033
|
+
|
|
1034
|
+
Accepts plain Python values (from unwrapped macro kwargs) and wraps
|
|
1035
|
+
them in TpyExpr nodes automatically.
|
|
1036
|
+
"""
|
|
1037
|
+
expr = _wrap_literal(expr)
|
|
1038
|
+
self.default_expr = expr
|
|
1039
|
+
self.has_default = expr is not None
|
|
1040
|
+
self.is_factory_default = is_factory
|
|
1041
|
+
if self._internal is not None:
|
|
1042
|
+
self._internal.default_expr = expr
|
|
1043
|
+
self._internal.default_value = default_value
|
|
1044
|
+
self._internal.is_factory_default = is_factory
|
|
1045
|
+
|
|
1046
|
+
def clear_default(self) -> None:
|
|
1047
|
+
"""Remove the field's default (e.g. after unwrapping a descriptor)."""
|
|
1048
|
+
self.default_expr = None
|
|
1049
|
+
self.has_default = False
|
|
1050
|
+
self.is_factory_default = False
|
|
1051
|
+
if self._internal is not None:
|
|
1052
|
+
self._internal.default_expr = None
|
|
1053
|
+
self._internal.default_value = None
|
|
1054
|
+
self._internal.is_factory_default = False
|
|
1055
|
+
|
|
1056
|
+
def to_internal(self) -> InternalFieldInfo:
|
|
1057
|
+
"""Return the underlying compiler FieldInfo."""
|
|
1058
|
+
if self._internal is not None:
|
|
1059
|
+
return self._internal
|
|
1060
|
+
assert self.type._tpy_type is not None
|
|
1061
|
+
return InternalFieldInfo(
|
|
1062
|
+
name=self.name,
|
|
1063
|
+
type=self.type._tpy_type,
|
|
1064
|
+
default_expr=self.default_expr,
|
|
1065
|
+
is_factory_default=self.is_factory_default,
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
# ---------------------------------------------------------------------------
|
|
1070
|
+
# MethodInfo -- read-only view of a method
|
|
1071
|
+
# ---------------------------------------------------------------------------
|
|
1072
|
+
|
|
1073
|
+
@dataclass
|
|
1074
|
+
class MethodInfo:
|
|
1075
|
+
"""Method metadata exposed to macros (read-only)."""
|
|
1076
|
+
name: str
|
|
1077
|
+
is_readonly: bool = False
|
|
1078
|
+
is_staticmethod: bool = False
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
# ---------------------------------------------------------------------------
|
|
1082
|
+
# ClassInfo -- mutable wrapper for macro class transformation
|
|
1083
|
+
# ---------------------------------------------------------------------------
|
|
1084
|
+
|
|
1085
|
+
class ClassInfo:
|
|
1086
|
+
"""Class metadata passed to class macros.
|
|
1087
|
+
|
|
1088
|
+
Macros inspect fields/methods and can add methods, set flags, and
|
|
1089
|
+
set match_args. Mutations are applied back to the TpyRecord
|
|
1090
|
+
via ``apply_to_record()``.
|
|
1091
|
+
"""
|
|
1092
|
+
|
|
1093
|
+
def __init__(
|
|
1094
|
+
self,
|
|
1095
|
+
record: TpyRecord,
|
|
1096
|
+
ctx: SemanticContext,
|
|
1097
|
+
macro_origin: str | None = None,
|
|
1098
|
+
) -> None:
|
|
1099
|
+
self._record = record
|
|
1100
|
+
self._ctx = ctx
|
|
1101
|
+
self._added_methods: list[TpyFunction] = []
|
|
1102
|
+
self._match_args: list[str] | None = None
|
|
1103
|
+
# Name of the macro currently expanding through this ClassInfo
|
|
1104
|
+
# (e.g. "dataclass", "model"). Stamped on every TpyFunction passed
|
|
1105
|
+
# to `add_method` so diagnostics can attribute the synthesis to a
|
|
1106
|
+
# specific decorator without each call site repeating the name.
|
|
1107
|
+
self._macro_origin: str | None = macro_origin
|
|
1108
|
+
|
|
1109
|
+
# Populate read-only views
|
|
1110
|
+
self.name: str = record.name
|
|
1111
|
+
self.type_params: list[str] = list(record.type_params)
|
|
1112
|
+
self.fields: list[FieldInfo] = [
|
|
1113
|
+
FieldInfo.from_internal(f) for f in record.fields
|
|
1114
|
+
]
|
|
1115
|
+
self.parent: TypeInfo | None = (
|
|
1116
|
+
TypeInfo.from_tpy_type(record.bases[0])
|
|
1117
|
+
if record.bases else None
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
# -- Flags (settable by macro, propagated to TpyRecord) --
|
|
1121
|
+
|
|
1122
|
+
@property
|
|
1123
|
+
def is_frozen(self) -> bool:
|
|
1124
|
+
return self._record.is_frozen
|
|
1125
|
+
|
|
1126
|
+
@is_frozen.setter
|
|
1127
|
+
def is_frozen(self, value: bool) -> None:
|
|
1128
|
+
self._record.is_frozen = value
|
|
1129
|
+
|
|
1130
|
+
# -- Read methods --
|
|
1131
|
+
|
|
1132
|
+
@property
|
|
1133
|
+
def methods(self) -> list[MethodInfo]:
|
|
1134
|
+
"""Current methods on the class (including macro-added ones)."""
|
|
1135
|
+
result = [
|
|
1136
|
+
MethodInfo(
|
|
1137
|
+
name=m.name,
|
|
1138
|
+
is_readonly=m.is_readonly,
|
|
1139
|
+
is_staticmethod=m.is_staticmethod,
|
|
1140
|
+
)
|
|
1141
|
+
for m in self._record.methods
|
|
1142
|
+
]
|
|
1143
|
+
result.extend(
|
|
1144
|
+
MethodInfo(
|
|
1145
|
+
name=m.name,
|
|
1146
|
+
is_readonly=m.is_readonly,
|
|
1147
|
+
is_staticmethod=m.is_staticmethod,
|
|
1148
|
+
)
|
|
1149
|
+
for m in self._added_methods
|
|
1150
|
+
)
|
|
1151
|
+
return result
|
|
1152
|
+
|
|
1153
|
+
def has_method(self, name: str) -> bool:
|
|
1154
|
+
"""Check if the class declares (or has had a macro add) a
|
|
1155
|
+
method with the given name. Self-only; inherited methods do
|
|
1156
|
+
not count -- mirrors CPython's `name in cls.__dict__` check
|
|
1157
|
+
that decorators like `@dataclass` use to decide whether to
|
|
1158
|
+
synthesize a method (overriding a parent's is intentional).
|
|
1159
|
+
Macros that want CPython's MRO semantics use
|
|
1160
|
+
`has_method_or_inherited` instead.
|
|
1161
|
+
"""
|
|
1162
|
+
for m in self._record.methods:
|
|
1163
|
+
if m.name == name:
|
|
1164
|
+
return True
|
|
1165
|
+
for m in self._added_methods:
|
|
1166
|
+
if m.name == name:
|
|
1167
|
+
return True
|
|
1168
|
+
return False
|
|
1169
|
+
|
|
1170
|
+
def _iter_parent_records(self) -> Iterator[RecordInfo]:
|
|
1171
|
+
"""Yield each base RecordInfo registered in the registry, in
|
|
1172
|
+
declaration order. Skips non-NominalType bases and NominalType
|
|
1173
|
+
bases that resolve to protocols or non-record types (builtin
|
|
1174
|
+
markers, primitives) -- those don't appear in `registry.records`.
|
|
1175
|
+
Walks self._record.bases directly because at macro-application
|
|
1176
|
+
time the self RecordInfo's `mro_ancestors` is still empty
|
|
1177
|
+
(populated post-macro-phase).
|
|
1178
|
+
"""
|
|
1179
|
+
registry = self._ctx.registry
|
|
1180
|
+
for base in self._record.bases:
|
|
1181
|
+
if not isinstance(base, NominalType):
|
|
1182
|
+
continue
|
|
1183
|
+
parent_info = registry.get_record(base.name)
|
|
1184
|
+
if parent_info is not None:
|
|
1185
|
+
yield parent_info
|
|
1186
|
+
|
|
1187
|
+
def has_method_or_inherited(self, name: str) -> bool:
|
|
1188
|
+
"""Like `has_method`, but also walks parent records via the
|
|
1189
|
+
registry. Use when CPython's MRO-based `hasattr` semantics
|
|
1190
|
+
are the right model -- e.g. `@total_ordering` accepting
|
|
1191
|
+
`__lt__` defined on a base class as the anchor.
|
|
1192
|
+
"""
|
|
1193
|
+
if self.has_method(name):
|
|
1194
|
+
return True
|
|
1195
|
+
registry = self._ctx.registry
|
|
1196
|
+
for parent_info in self._iter_parent_records():
|
|
1197
|
+
if registry.get_method_overloads_with_parents(parent_info, name):
|
|
1198
|
+
return True
|
|
1199
|
+
return False
|
|
1200
|
+
|
|
1201
|
+
def get_parent_fields(self) -> list[FieldInfo]:
|
|
1202
|
+
"""Get all fields from parent record (including inherited).
|
|
1203
|
+
|
|
1204
|
+
Returns fields for the first base that is a registered record,
|
|
1205
|
+
or empty list if no record parent. The caller decides whether
|
|
1206
|
+
to use these based on its own eligibility checks.
|
|
1207
|
+
"""
|
|
1208
|
+
for parent_info in self._iter_parent_records():
|
|
1209
|
+
all_parent = self._ctx.registry.get_all_fields(parent_info)
|
|
1210
|
+
return [FieldInfo.from_internal(f) for f in all_parent]
|
|
1211
|
+
return []
|
|
1212
|
+
|
|
1213
|
+
# -- Mutation methods --
|
|
1214
|
+
|
|
1215
|
+
def add_method(self, func: TpyFunction) -> None:
|
|
1216
|
+
"""Add a method from a TpyFunction AST node (power user API).
|
|
1217
|
+
|
|
1218
|
+
``is_macro_generated`` is always set True -- any method added
|
|
1219
|
+
via this API is by definition macro-generated. ``macro_origin``
|
|
1220
|
+
is inherited from the currently-expanding macro (set by the
|
|
1221
|
+
dispatcher when invoking the macro function), so individual
|
|
1222
|
+
synthesis sites don't need to repeat the macro name.
|
|
1223
|
+
"""
|
|
1224
|
+
func.is_macro_generated = True
|
|
1225
|
+
if self._macro_origin is not None and func.macro_origin is None:
|
|
1226
|
+
func.macro_origin = self._macro_origin
|
|
1227
|
+
self._added_methods.append(func)
|
|
1228
|
+
|
|
1229
|
+
def add_method_from_source(self, source: str) -> None:
|
|
1230
|
+
"""Parse a method definition from TPy source and add it.
|
|
1231
|
+
|
|
1232
|
+
Equivalent to ``self.add_method(ast.quote_fun(source))``.
|
|
1233
|
+
"""
|
|
1234
|
+
self.add_method(ast.quote_fun(source))
|
|
1235
|
+
|
|
1236
|
+
def defer_until_macros_complete(
|
|
1237
|
+
self, callback: Callable[['ClassInfo'], None],
|
|
1238
|
+
) -> None:
|
|
1239
|
+
"""Register a callback to run after all eager class macros on
|
|
1240
|
+
this record have applied. Use when the macro's behavior depends
|
|
1241
|
+
on the final method set produced by composition -- e.g.
|
|
1242
|
+
``@total_ordering`` picks an ordering anchor among the four
|
|
1243
|
+
rich-comparison dunders, some of which a peer macro
|
|
1244
|
+
(``@dataclass(order=True)``) may add. The callback receives a
|
|
1245
|
+
fresh ClassInfo bound to the same record; mutations are written
|
|
1246
|
+
back via the deferred-pass runner.
|
|
1247
|
+
"""
|
|
1248
|
+
# Snapshot the origin so the deferred pass attributes the
|
|
1249
|
+
# callback's mutations to the macro that registered it.
|
|
1250
|
+
self._record.pending_deferred_macros.append((self._macro_origin, callback))
|
|
1251
|
+
|
|
1252
|
+
# TODO: property getter+setter emission from macros.
|
|
1253
|
+
# FragmentParser parses fragments one at a time, so the setter's
|
|
1254
|
+
# `@getter_name.setter` decorator has no `property_names` context
|
|
1255
|
+
# and falls through as "Unknown decorator". To support macro-
|
|
1256
|
+
# generated properties, add something like
|
|
1257
|
+
# add_property_pair(getter_source: str, setter_source: str) -> None
|
|
1258
|
+
# that parses both fragments together, threading the getter name
|
|
1259
|
+
# into FragmentParser so the setter resolves correctly. The method
|
|
1260
|
+
# expansion pipeline already handles property-setter `Own[T]`
|
|
1261
|
+
# wrapping, so the fix is purely at the FragmentParser routing
|
|
1262
|
+
# layer. Wait for a real use case before committing to an API shape.
|
|
1263
|
+
|
|
1264
|
+
def set_match_args(self, names: list[str]) -> None:
|
|
1265
|
+
"""Set positional match arg names (mirrors Python's __match_args__)."""
|
|
1266
|
+
self._match_args = list(names)
|
|
1267
|
+
|
|
1268
|
+
def is_parent_frozen(self) -> tuple[bool, str] | None:
|
|
1269
|
+
"""Check if the first parent record is frozen.
|
|
1270
|
+
|
|
1271
|
+
Returns (is_frozen, parent_name) or None if no record parent.
|
|
1272
|
+
"""
|
|
1273
|
+
for parent_info in self._iter_parent_records():
|
|
1274
|
+
return (parent_info.is_frozen, parent_info.name)
|
|
1275
|
+
return None
|
|
1276
|
+
|
|
1277
|
+
def get_method_loc(self, name: str) -> Any:
|
|
1278
|
+
"""Get the source location of a method by name."""
|
|
1279
|
+
for m in self._record.methods:
|
|
1280
|
+
if m.name == name:
|
|
1281
|
+
return m.loc or self._record.loc
|
|
1282
|
+
return self._record.loc
|
|
1283
|
+
|
|
1284
|
+
# -- Diagnostics --
|
|
1285
|
+
|
|
1286
|
+
def warning(self, msg: str, loc: Any = None) -> None:
|
|
1287
|
+
"""Emit a compiler warning."""
|
|
1288
|
+
self._ctx.warning_from_loc(msg, loc or self._record.loc)
|
|
1289
|
+
|
|
1290
|
+
def error(self, msg: str, loc: Any = None) -> NoReturn:
|
|
1291
|
+
"""Raise a compile error."""
|
|
1292
|
+
from .diagnostics import SemanticError
|
|
1293
|
+
raise SemanticError(msg, loc or self._record.loc)
|
|
1294
|
+
|
|
1295
|
+
# -- Apply mutations back to TpyRecord --
|
|
1296
|
+
|
|
1297
|
+
def apply_to_record(self) -> None:
|
|
1298
|
+
"""Write macro mutations back to the TpyRecord."""
|
|
1299
|
+
for func in self._added_methods:
|
|
1300
|
+
if func.name == "__init__":
|
|
1301
|
+
self._record.methods.insert(0, func)
|
|
1302
|
+
else:
|
|
1303
|
+
self._record.methods.append(func)
|
|
1304
|
+
|
|
1305
|
+
def get_match_args(self) -> tuple[str, ...] | None:
|
|
1306
|
+
"""Return the match args set by the macro, or None."""
|
|
1307
|
+
return tuple(self._match_args) if self._match_args is not None else None
|
|
1308
|
+
|
|
1309
|
+
|
|
1310
|
+
# ---------------------------------------------------------------------------
|
|
1311
|
+
# AstBuilder -- AST node construction for macro modules
|
|
1312
|
+
# ---------------------------------------------------------------------------
|
|
1313
|
+
|
|
1314
|
+
class AstBuilder:
|
|
1315
|
+
"""Builder for AST nodes. Macro modules use this instead of importing
|
|
1316
|
+
compiler-internal node types directly.
|
|
1317
|
+
|
|
1318
|
+
Usage: ``from tpyc.macro_api import ast`` then ``ast.name("x")``.
|
|
1319
|
+
"""
|
|
1320
|
+
|
|
1321
|
+
def __init__(self) -> None:
|
|
1322
|
+
self._tmp_counter = 0
|
|
1323
|
+
|
|
1324
|
+
def reset_tmp_counter(self) -> None:
|
|
1325
|
+
"""Reset the fresh-name counter. Called automatically before each macro invocation."""
|
|
1326
|
+
self._tmp_counter = 0
|
|
1327
|
+
|
|
1328
|
+
def fresh_tmp(self, hint: str = "v") -> str:
|
|
1329
|
+
"""Generate a unique temporary variable name."""
|
|
1330
|
+
self._tmp_counter += 1
|
|
1331
|
+
return f"__{hint}_{self._tmp_counter}"
|
|
1332
|
+
|
|
1333
|
+
# -- Expressions --
|
|
1334
|
+
|
|
1335
|
+
def name(self, n: str) -> Expr:
|
|
1336
|
+
"""Create a name reference. Dotted names (e.g. 'mylog.infra') are
|
|
1337
|
+
decomposed into a field-access chain so module resolution works."""
|
|
1338
|
+
if not n or ".." in n or n.startswith(".") or n.endswith("."):
|
|
1339
|
+
raise MacroError(f"ast.name(): invalid name {n!r}")
|
|
1340
|
+
if "." not in n:
|
|
1341
|
+
return TpyName(n)
|
|
1342
|
+
parts = n.split(".")
|
|
1343
|
+
result: Expr = TpyName(parts[0])
|
|
1344
|
+
for part in parts[1:]:
|
|
1345
|
+
result = TpyFieldAccess(obj=result, field=part)
|
|
1346
|
+
return result
|
|
1347
|
+
|
|
1348
|
+
def call(self, func: str, args: list[Expr] | None = None,
|
|
1349
|
+
call_type: TpyType | None = None) -> Expr:
|
|
1350
|
+
return TpyCall(func=TpyName(func), args=args or [], call_type=call_type)
|
|
1351
|
+
|
|
1352
|
+
def method_call(self, obj: Expr, method: str, args: list[Expr] | None = None) -> Expr:
|
|
1353
|
+
return TpyMethodCall(obj=obj, method=method, args=args or [])
|
|
1354
|
+
|
|
1355
|
+
def field_access(self, obj: Expr, field: str) -> Expr:
|
|
1356
|
+
return TpyFieldAccess(obj=obj, field=field)
|
|
1357
|
+
|
|
1358
|
+
def binop(self, left: Expr, op: str, right: Expr) -> Expr:
|
|
1359
|
+
return TpyBinOp(left=left, op=op, right=right)
|
|
1360
|
+
|
|
1361
|
+
def subscript(self, obj: Expr, index: Expr) -> Expr:
|
|
1362
|
+
return TpySubscript(obj=obj, index=index)
|
|
1363
|
+
|
|
1364
|
+
def unary(self, op: str, operand: Expr) -> Expr:
|
|
1365
|
+
return TpyUnaryOp(op=op, operand=operand)
|
|
1366
|
+
|
|
1367
|
+
def str_lit(self, s: str) -> Expr:
|
|
1368
|
+
return TpyStrLiteral(value=s)
|
|
1369
|
+
|
|
1370
|
+
def int_lit(self, n: int) -> Expr:
|
|
1371
|
+
return TpyIntLiteral(value=n)
|
|
1372
|
+
|
|
1373
|
+
def float_lit(self, f: float) -> Expr:
|
|
1374
|
+
return TpyFloatLiteral(value=f)
|
|
1375
|
+
|
|
1376
|
+
def bool_lit(self, b: bool) -> Expr:
|
|
1377
|
+
return TpyBoolLiteral(value=b)
|
|
1378
|
+
|
|
1379
|
+
def none_lit(self) -> Expr:
|
|
1380
|
+
return TpyNoneLiteral()
|
|
1381
|
+
|
|
1382
|
+
def list_lit(self, elements: list[Expr] | None = None) -> Expr:
|
|
1383
|
+
return TpyArrayLiteral(elements=elements or [])
|
|
1384
|
+
|
|
1385
|
+
def dict_lit(self, keys: list[Expr] | None = None, values: list[Expr] | None = None) -> Expr:
|
|
1386
|
+
return TpyDictLiteral(keys=keys or [], values=values or [])
|
|
1387
|
+
|
|
1388
|
+
def tuple_lit(self, elements: list[Expr]) -> Expr:
|
|
1389
|
+
return TpyTupleLiteral(elements=elements)
|
|
1390
|
+
|
|
1391
|
+
def list_comprehension(self, element_expr: Expr,
|
|
1392
|
+
generator: TpyComprehensionGenerator) -> Expr:
|
|
1393
|
+
return TpyListComprehension(element_expr=element_expr, generator=generator)
|
|
1394
|
+
|
|
1395
|
+
def dict_comprehension(self, key_expr: Expr, value_expr: Expr,
|
|
1396
|
+
generator: TpyComprehensionGenerator) -> Expr:
|
|
1397
|
+
return TpyDictComprehension(key_expr=key_expr, value_expr=value_expr,
|
|
1398
|
+
generator=generator)
|
|
1399
|
+
|
|
1400
|
+
def set_comprehension(self, element_expr: Expr,
|
|
1401
|
+
generator: TpyComprehensionGenerator) -> Expr:
|
|
1402
|
+
return TpySetComprehension(element_expr=element_expr, generator=generator)
|
|
1403
|
+
|
|
1404
|
+
def if_expr(self, condition: Expr, then_expr: Expr, else_expr: Expr) -> Expr:
|
|
1405
|
+
return TpyIfExpr(condition=condition, then_expr=then_expr, else_expr=else_expr)
|
|
1406
|
+
|
|
1407
|
+
def comprehension_generator(self, var: str, iterable: Expr,
|
|
1408
|
+
conditions: list[Expr] | None = None,
|
|
1409
|
+
unpack_vars: list[str | None] | None = None) -> TpyComprehensionGenerator:
|
|
1410
|
+
return TpyComprehensionGenerator(var=var, iterable=iterable,
|
|
1411
|
+
conditions=conditions or [],
|
|
1412
|
+
unpack_vars=unpack_vars)
|
|
1413
|
+
|
|
1414
|
+
# -- Statements --
|
|
1415
|
+
|
|
1416
|
+
def var_decl(self, name: str, type: TpyType | None = None,
|
|
1417
|
+
init: Expr | None = None) -> Stmt:
|
|
1418
|
+
return TpyVarDecl(name=name, type=type, init=init)
|
|
1419
|
+
|
|
1420
|
+
def assign(self, target: Expr, value: Expr) -> Stmt:
|
|
1421
|
+
return TpyAssign(target=target, value=value)
|
|
1422
|
+
|
|
1423
|
+
def expr_stmt(self, expr: Expr) -> Stmt:
|
|
1424
|
+
return TpyExprStmt(expr=expr)
|
|
1425
|
+
|
|
1426
|
+
def return_(self, value: Expr | None = None) -> Stmt:
|
|
1427
|
+
return TpyReturn(value=value)
|
|
1428
|
+
|
|
1429
|
+
def if_(self, condition: Expr, then_body: list[Stmt],
|
|
1430
|
+
else_body: list[Stmt] | None = None) -> Stmt:
|
|
1431
|
+
return TpyIf(condition=condition, then_body=then_body,
|
|
1432
|
+
else_body=else_body or [])
|
|
1433
|
+
|
|
1434
|
+
def while_(self, condition: Expr, body: list[Stmt]) -> Stmt:
|
|
1435
|
+
return TpyWhile(condition=condition, body=body)
|
|
1436
|
+
|
|
1437
|
+
def for_each(self, var: str, iterable: Expr, body: list[Stmt],
|
|
1438
|
+
is_tuple_unpack: bool = False) -> Stmt:
|
|
1439
|
+
return TpyForEach(var=var, iterable=iterable, body=body,
|
|
1440
|
+
is_tuple_unpack=is_tuple_unpack)
|
|
1441
|
+
|
|
1442
|
+
def raise_(self, exception_type: str, args: list[Expr] | None = None) -> Stmt:
|
|
1443
|
+
if args:
|
|
1444
|
+
return TpyRaise(exception_type=exception_type, args=args, is_call_form=True)
|
|
1445
|
+
return TpyRaise(exception_type=exception_type)
|
|
1446
|
+
|
|
1447
|
+
def assert_(self, condition: Expr, message: Expr | None = None) -> Stmt:
|
|
1448
|
+
return TpyAssert(condition=condition, message=message)
|
|
1449
|
+
|
|
1450
|
+
def try_(self, try_body: list[Stmt], handlers: list | None = None,
|
|
1451
|
+
else_body: list[Stmt] | None = None,
|
|
1452
|
+
finally_body: list[Stmt] | None = None) -> Stmt:
|
|
1453
|
+
return TpyTry(try_body=try_body, handlers=handlers or [],
|
|
1454
|
+
else_body=else_body or [], finally_body=finally_body or [])
|
|
1455
|
+
|
|
1456
|
+
def except_handler(self, exception_type: str | None = None,
|
|
1457
|
+
binding: str | None = None,
|
|
1458
|
+
body: list[Stmt] | None = None) -> TpyExceptHandler:
|
|
1459
|
+
return TpyExceptHandler(exception_type=exception_type,
|
|
1460
|
+
binding=binding, body=body or [])
|
|
1461
|
+
|
|
1462
|
+
def match(self, subject: Expr, cases: list) -> Stmt:
|
|
1463
|
+
return TpyMatch(subject=subject, cases=cases)
|
|
1464
|
+
|
|
1465
|
+
def match_case(self, pattern: Any, body: list[Stmt],
|
|
1466
|
+
guard: Expr | None = None) -> TpyMatchCase:
|
|
1467
|
+
return TpyMatchCase(pattern=pattern, guard=guard, body=body)
|
|
1468
|
+
|
|
1469
|
+
def literal_pattern(self, value: int | float | str | bool | None) -> TpyLiteralPattern:
|
|
1470
|
+
return TpyLiteralPattern(value=value)
|
|
1471
|
+
|
|
1472
|
+
def wildcard_pattern(self) -> TpyWildcardPattern:
|
|
1473
|
+
return TpyWildcardPattern()
|
|
1474
|
+
|
|
1475
|
+
def tuple_unpack(self, targets: list[str | None], value: Expr) -> Stmt:
|
|
1476
|
+
return TpyTupleUnpack(targets=targets, value=value)
|
|
1477
|
+
|
|
1478
|
+
# -- Cloning --
|
|
1479
|
+
|
|
1480
|
+
def clone(self, expr: Expr) -> Expr:
|
|
1481
|
+
"""Deep-copy an expression tree. Use when the same logical expression
|
|
1482
|
+
must appear in multiple places (e.g. condition and body of a ternary)
|
|
1483
|
+
to avoid sema overwriting resolved types on shared nodes."""
|
|
1484
|
+
return copy.deepcopy(expr)
|
|
1485
|
+
|
|
1486
|
+
# -- Introspection --
|
|
1487
|
+
|
|
1488
|
+
def get_field_name(self, expr: Expr) -> str | None:
|
|
1489
|
+
"""If expr is a field access node, return the field name. Otherwise None."""
|
|
1490
|
+
if isinstance(expr, TpyFieldAccess):
|
|
1491
|
+
return expr.field
|
|
1492
|
+
return None
|
|
1493
|
+
|
|
1494
|
+
def get_name(self, expr: Expr) -> str | None:
|
|
1495
|
+
"""If expr is a simple name node, return the name. Otherwise None."""
|
|
1496
|
+
if isinstance(expr, TpyName):
|
|
1497
|
+
return expr.name
|
|
1498
|
+
return None
|
|
1499
|
+
|
|
1500
|
+
# -- Functions --
|
|
1501
|
+
|
|
1502
|
+
def function(self, name: str, params: list[tuple[str, TpyType]],
|
|
1503
|
+
return_type: TpyType, body: list[Stmt], *,
|
|
1504
|
+
is_method: bool = False, is_staticmethod: bool = False,
|
|
1505
|
+
is_readonly: bool = False, readonly_opt_out: bool = False,
|
|
1506
|
+
error_return: str | None = None,
|
|
1507
|
+
defaults: list[Expr | None] | None = None,
|
|
1508
|
+
hides_parent: bool = False) -> Function:
|
|
1509
|
+
return TpyFunction(
|
|
1510
|
+
name=name, params=params, return_type=return_type, body=body,
|
|
1511
|
+
is_method=is_method, is_staticmethod=is_staticmethod,
|
|
1512
|
+
is_readonly=is_readonly, readonly_opt_out=readonly_opt_out,
|
|
1513
|
+
error_return=error_return, defaults=defaults or [],
|
|
1514
|
+
hides_parent=hides_parent,
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
# -- Source-based quoting --
|
|
1518
|
+
|
|
1519
|
+
def _quote(
|
|
1520
|
+
self, source: str, kind: Literal["function", "statements", "expression"],
|
|
1521
|
+
) -> list[Stmt] | Expr | Function:
|
|
1522
|
+
try:
|
|
1523
|
+
return _FragmentParser.parse_fragment(source, kind=kind)
|
|
1524
|
+
except SyntaxError as e:
|
|
1525
|
+
clean = textwrap.dedent(source).strip()
|
|
1526
|
+
lines = clean.splitlines()
|
|
1527
|
+
lineno = (e.lineno or 1) - 1
|
|
1528
|
+
bad_line = lines[lineno].strip() if 0 <= lineno < len(lines) else lines[0]
|
|
1529
|
+
raise MacroError(f"syntax error in quoted source: `{bad_line}`") from None
|
|
1530
|
+
except _ParseError as e:
|
|
1531
|
+
preview = textwrap.dedent(source).strip().split("\n")[0]
|
|
1532
|
+
if len(preview) > 60:
|
|
1533
|
+
preview = preview[:57] + "..."
|
|
1534
|
+
raise MacroError(f"{e} (source: `{preview}`)") from None
|
|
1535
|
+
|
|
1536
|
+
def quote(self, source: str) -> list[Stmt]:
|
|
1537
|
+
"""Parse TPy statements from a source string."""
|
|
1538
|
+
return self._quote(source, "statements")
|
|
1539
|
+
|
|
1540
|
+
def quote_expr(self, source: str) -> Expr:
|
|
1541
|
+
"""Parse a single TPy expression from a source string."""
|
|
1542
|
+
return self._quote(source, "expression")
|
|
1543
|
+
|
|
1544
|
+
def quote_fun(self, source: str) -> Function:
|
|
1545
|
+
"""Parse a complete function definition from a source string.
|
|
1546
|
+
|
|
1547
|
+
Decorators are not supported. To mark as staticmethod, set
|
|
1548
|
+
func.is_staticmethod = True on the returned function.
|
|
1549
|
+
"""
|
|
1550
|
+
return self._quote(source, "function")
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
# Module-level singleton
|
|
1554
|
+
ast = AstBuilder()
|
|
1555
|
+
|
|
1556
|
+
|
|
1557
|
+
# ---------------------------------------------------------------------------
|
|
1558
|
+
# TypeBuilder -- type construction for macro modules
|
|
1559
|
+
# ---------------------------------------------------------------------------
|
|
1560
|
+
|
|
1561
|
+
class TypeBuilder:
|
|
1562
|
+
"""Builder for TpyType objects. Macro modules use this instead of importing
|
|
1563
|
+
compiler-internal type classes directly.
|
|
1564
|
+
|
|
1565
|
+
Usage: ``from tpyc.macro_api import types`` then ``types.named("Foo")``.
|
|
1566
|
+
"""
|
|
1567
|
+
|
|
1568
|
+
# -- Constructors --
|
|
1569
|
+
|
|
1570
|
+
def named(self, name: str) -> TpyType:
|
|
1571
|
+
return NominalType(name)
|
|
1572
|
+
|
|
1573
|
+
def own(self, inner: TpyType) -> TpyType:
|
|
1574
|
+
return OwnType(inner)
|
|
1575
|
+
|
|
1576
|
+
def optional(self, inner: TpyType) -> TpyType:
|
|
1577
|
+
return OptionalType(inner)
|
|
1578
|
+
|
|
1579
|
+
def list(self, element_type: TpyType) -> TpyType:
|
|
1580
|
+
return make_list(element_type)
|
|
1581
|
+
|
|
1582
|
+
def span(self, element_type: TpyType) -> TpyType:
|
|
1583
|
+
return make_span(element_type)
|
|
1584
|
+
|
|
1585
|
+
def dict(self, key_type: TpyType, value_type: TpyType) -> TpyType:
|
|
1586
|
+
return make_dict(key_type, value_type)
|
|
1587
|
+
|
|
1588
|
+
def set(self, element_type: TpyType) -> TpyType:
|
|
1589
|
+
return make_set(element_type)
|
|
1590
|
+
|
|
1591
|
+
def tuple(self, element_types: tuple[TpyType, ...] | list[TpyType]) -> TpyType:
|
|
1592
|
+
if isinstance(element_types, list):
|
|
1593
|
+
element_types = tuple(element_types)
|
|
1594
|
+
return TupleType(element_types)
|
|
1595
|
+
|
|
1596
|
+
def union(self, member_types: tuple[TpyType, ...] | list[TpyType]) -> TpyType:
|
|
1597
|
+
if isinstance(member_types, list):
|
|
1598
|
+
member_types = tuple(member_types)
|
|
1599
|
+
return UnionType(member_types)
|
|
1600
|
+
|
|
1601
|
+
# -- Singleton types --
|
|
1602
|
+
|
|
1603
|
+
@property
|
|
1604
|
+
def void(self) -> TpyType:
|
|
1605
|
+
return _VOID
|
|
1606
|
+
|
|
1607
|
+
@property
|
|
1608
|
+
def str(self) -> TpyType:
|
|
1609
|
+
return _STR
|
|
1610
|
+
|
|
1611
|
+
@property
|
|
1612
|
+
def str_view(self) -> TpyType:
|
|
1613
|
+
return _STRVIEW
|
|
1614
|
+
|
|
1615
|
+
@property
|
|
1616
|
+
def bool(self) -> TpyType:
|
|
1617
|
+
return _BOOL
|
|
1618
|
+
|
|
1619
|
+
# -- Fixed-width integers --
|
|
1620
|
+
|
|
1621
|
+
@property
|
|
1622
|
+
def int8(self) -> TpyType:
|
|
1623
|
+
return INT8
|
|
1624
|
+
|
|
1625
|
+
@property
|
|
1626
|
+
def int16(self) -> TpyType:
|
|
1627
|
+
return INT16
|
|
1628
|
+
|
|
1629
|
+
@property
|
|
1630
|
+
def int32(self) -> TpyType:
|
|
1631
|
+
return INT32
|
|
1632
|
+
|
|
1633
|
+
@property
|
|
1634
|
+
def int64(self) -> TpyType:
|
|
1635
|
+
return INT64
|
|
1636
|
+
|
|
1637
|
+
@property
|
|
1638
|
+
def uint8(self) -> TpyType:
|
|
1639
|
+
return UINT8
|
|
1640
|
+
|
|
1641
|
+
@property
|
|
1642
|
+
def uint16(self) -> TpyType:
|
|
1643
|
+
return UINT16
|
|
1644
|
+
|
|
1645
|
+
@property
|
|
1646
|
+
def uint32(self) -> TpyType:
|
|
1647
|
+
return UINT32
|
|
1648
|
+
|
|
1649
|
+
@property
|
|
1650
|
+
def uint64(self) -> TpyType:
|
|
1651
|
+
return UINT64
|
|
1652
|
+
|
|
1653
|
+
# -- Floating point --
|
|
1654
|
+
|
|
1655
|
+
@property
|
|
1656
|
+
def float(self) -> TpyType:
|
|
1657
|
+
return _FLOAT
|
|
1658
|
+
|
|
1659
|
+
@property
|
|
1660
|
+
def float64(self) -> TpyType:
|
|
1661
|
+
return _FLOAT
|
|
1662
|
+
|
|
1663
|
+
@property
|
|
1664
|
+
def float32(self) -> TpyType:
|
|
1665
|
+
return _FLOAT32
|
|
1666
|
+
|
|
1667
|
+
# -- Arbitrary precision --
|
|
1668
|
+
|
|
1669
|
+
@property
|
|
1670
|
+
def bigint(self) -> TpyType:
|
|
1671
|
+
return _BIGINT
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
# Module-level singleton
|
|
1675
|
+
types = TypeBuilder()
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
# ---------------------------------------------------------------------------
|
|
1679
|
+
# AST builder helpers for common macro patterns
|
|
1680
|
+
# ---------------------------------------------------------------------------
|
|
1681
|
+
|
|
1682
|
+
def _wrap_literal(value: object) -> TpyExpr | None:
|
|
1683
|
+
"""Wrap a plain Python value in a TpyExpr node. Pass through TpyExpr unchanged."""
|
|
1684
|
+
if isinstance(value, TpyExpr):
|
|
1685
|
+
return value
|
|
1686
|
+
if value is None:
|
|
1687
|
+
return TpyNoneLiteral()
|
|
1688
|
+
if isinstance(value, bool):
|
|
1689
|
+
return TpyBoolLiteral(value=value)
|
|
1690
|
+
if isinstance(value, int):
|
|
1691
|
+
return TpyIntLiteral(value=value)
|
|
1692
|
+
if isinstance(value, float):
|
|
1693
|
+
return TpyFloatLiteral(value=value)
|
|
1694
|
+
if isinstance(value, str):
|
|
1695
|
+
return TpyStrLiteral(value=value)
|
|
1696
|
+
return value # non-literal (e.g. factory callable)
|
|
1697
|
+
|
|
1698
|
+
|
|
1699
|
+
def expr_to_cpp_default(expr: TpyExpr | int | float | str | bool | None) -> str | None:
|
|
1700
|
+
"""Convert a TpyExpr or plain value to a C++ default value literal string, or None."""
|
|
1701
|
+
# Normalize plain Python values to TpyExpr first
|
|
1702
|
+
if not isinstance(expr, TpyExpr) and expr is not None:
|
|
1703
|
+
expr = _wrap_literal(expr)
|
|
1704
|
+
if isinstance(expr, TpyIntLiteral):
|
|
1705
|
+
return str(expr.value)
|
|
1706
|
+
if isinstance(expr, TpyFloatLiteral):
|
|
1707
|
+
return str(expr.value)
|
|
1708
|
+
if isinstance(expr, TpyBoolLiteral):
|
|
1709
|
+
return "true" if expr.value else "false"
|
|
1710
|
+
if isinstance(expr, TpyStrLiteral):
|
|
1711
|
+
escaped = expr.value.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
|
|
1712
|
+
return f'"{escaped}"'
|
|
1713
|
+
if isinstance(expr, TpyNoneLiteral):
|
|
1714
|
+
return "std::nullopt"
|
|
1715
|
+
if isinstance(expr, TpyUnaryOp) and expr.op == "-":
|
|
1716
|
+
inner = expr_to_cpp_default(expr.operand)
|
|
1717
|
+
if inner is not None:
|
|
1718
|
+
return f"-{inner}"
|
|
1719
|
+
if isinstance(expr, TpyCall) and isinstance(expr.func, TpyName) and expr.func_name in _FIXED_INT_NAMES:
|
|
1720
|
+
if not expr.args:
|
|
1721
|
+
return "0"
|
|
1722
|
+
if len(expr.args) == 1:
|
|
1723
|
+
return expr_to_cpp_default(expr.args[0])
|
|
1724
|
+
return None
|