tpy-lang 0.3.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tpy_lang-0.3.0.dev0.dist-info/METADATA +151 -0
- tpy_lang-0.3.0.dev0.dist-info/RECORD +333 -0
- tpy_lang-0.3.0.dev0.dist-info/WHEEL +4 -0
- tpy_lang-0.3.0.dev0.dist-info/entry_points.txt +3 -0
- tpyc/__init__.py +104 -0
- tpyc/__main__.py +6 -0
- tpyc/_buildinfo.py +1 -0
- tpyc/_data/docs/LANGUAGE_FEATURES.md +6278 -0
- tpyc/_data/docs/STDLIB_ROADMAP.md +1258 -0
- tpyc/_data/docs/TPY_FOR_AGENTS.md +556 -0
- tpyc/_data/lib/tpy/_bindings/__init__.py +6 -0
- tpyc/_data/lib/tpy/_bindings/pcre2.py +173 -0
- tpyc/_data/lib/tpy/_bindings/posix_socket.py +161 -0
- tpyc/_data/lib/tpy/_functools_macros.py +80 -0
- tpyc/_data/lib/tpy/_macro_helpers.py +161 -0
- tpyc/_data/lib/tpy/argparse.py +2062 -0
- tpyc/_data/lib/tpy/asyncio/__init__.py +744 -0
- tpyc/_data/lib/tpy/asyncio/_executor.py +515 -0
- tpyc/_data/lib/tpy/base64.py +410 -0
- tpyc/_data/lib/tpy/bisect.py +39 -0
- tpyc/_data/lib/tpy/builtins.py +38 -0
- tpyc/_data/lib/tpy/dataclasses.py +354 -0
- tpyc/_data/lib/tpy/enum.py +23 -0
- tpyc/_data/lib/tpy/functools.py +33 -0
- tpyc/_data/lib/tpy/hashlib.py +206 -0
- tpyc/_data/lib/tpy/heapq.py +118 -0
- tpyc/_data/lib/tpy/io.py +395 -0
- tpyc/_data/lib/tpy/json.py +221 -0
- tpyc/_data/lib/tpy/math.py +406 -0
- tpyc/_data/lib/tpy/random.py +597 -0
- tpyc/_data/lib/tpy/re.py +467 -0
- tpyc/_data/lib/tpy/socket.py +379 -0
- tpyc/_data/lib/tpy/struct.py +178 -0
- tpyc/_data/lib/tpy/sys.py +40 -0
- tpyc/_data/lib/tpy/time.py +39 -0
- tpyc/_data/lib/tpy/tpy/__init__.py +78 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/__init__.py +10 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_decorators.py +37 -0
- tpyc/_data/lib/tpy/tpy/_bootstrap/_extern.py +64 -0
- tpyc/_data/lib/tpy/tpy/_builtins/__init__.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_bytes.py +378 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_dict.py +151 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_exceptions.py +125 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_funcs.py +681 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_io.py +97 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_list.py +127 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_range.py +52 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_set.py +139 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_super.py +11 -0
- tpyc/_data/lib/tpy/tpy/_builtins/_types.py +661 -0
- tpyc/_data/lib/tpy/tpy/_core/__init__.py +23 -0
- tpyc/_data/lib/tpy/tpy/_core/_bytes_view.py +129 -0
- tpyc/_data/lib/tpy/tpy/_core/_containers.py +137 -0
- tpyc/_data/lib/tpy/tpy/_core/_functions.py +40 -0
- tpyc/_data/lib/tpy/tpy/_core/_types.py +2061 -0
- tpyc/_data/lib/tpy/tpy/_typing/__init__.py +77 -0
- tpyc/_data/lib/tpy/tpy/_version.py +29 -0
- tpyc/_data/lib/tpy/tpy/bits.py +28 -0
- tpyc/_data/lib/tpy/tpy/coro/__init__.py +127 -0
- tpyc/_data/lib/tpy/tpy/extern.py +8 -0
- tpyc/_data/lib/tpy/tpy/mem.py +49 -0
- tpyc/_data/lib/tpy/tpy/unsafe.py +195 -0
- tpyc/_data/lib/tpy/tpy/version.py +21 -0
- tpyc/_data/lib/tpy/typing.py +13 -0
- tpyc/_data/runtime/cpp/include/tpy/any.hpp +461 -0
- tpyc/_data/runtime/cpp/include/tpy/as_ostream.hpp +117 -0
- tpyc/_data/runtime/cpp/include/tpy/async.hpp +76 -0
- tpyc/_data/runtime/cpp/include/tpy/bigint.hpp +1343 -0
- tpyc/_data/runtime/cpp/include/tpy/builtins.hpp +400 -0
- tpyc/_data/runtime/cpp/include/tpy/bytes_ops.hpp +469 -0
- tpyc/_data/runtime/cpp/include/tpy/container_ops.hpp +487 -0
- tpyc/_data/runtime/cpp/include/tpy/copy_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/core.hpp +558 -0
- tpyc/_data/runtime/cpp/include/tpy/dict_ops.hpp +289 -0
- tpyc/_data/runtime/cpp/include/tpy/dunder.hpp +750 -0
- tpyc/_data/runtime/cpp/include/tpy/dynamic.hpp +44 -0
- tpyc/_data/runtime/cpp/include/tpy/enum.hpp +40 -0
- tpyc/_data/runtime/cpp/include/tpy/file.hpp +245 -0
- tpyc/_data/runtime/cpp/include/tpy/fixed_int.hpp +317 -0
- tpyc/_data/runtime/cpp/include/tpy/format.hpp +954 -0
- tpyc/_data/runtime/cpp/include/tpy/frame_slot.hpp +120 -0
- tpyc/_data/runtime/cpp/include/tpy/generator.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/iterable_ops.hpp +122 -0
- tpyc/_data/runtime/cpp/include/tpy/itertools.hpp +749 -0
- tpyc/_data/runtime/cpp/include/tpy/next_iter.hpp +82 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_map.hpp +518 -0
- tpyc/_data/runtime/cpp/include/tpy/ordered_set.hpp +337 -0
- tpyc/_data/runtime/cpp/include/tpy/own_iter.hpp +54 -0
- tpyc/_data/runtime/cpp/include/tpy/pascal_graph_sdl.hpp +192 -0
- tpyc/_data/runtime/cpp/include/tpy/printing.hpp +302 -0
- tpyc/_data/runtime/cpp/include/tpy/protocols.hpp +61 -0
- tpyc/_data/runtime/cpp/include/tpy/range.hpp +115 -0
- tpyc/_data/runtime/cpp/include/tpy/ranges.hpp +212 -0
- tpyc/_data/runtime/cpp/include/tpy/set_ops.hpp +265 -0
- tpyc/_data/runtime/cpp/include/tpy/slice.hpp +47 -0
- tpyc/_data/runtime/cpp/include/tpy/span_iter.hpp +42 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/math.hpp +41 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/pcre2_h.hpp +96 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/random.hpp +25 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/socket_h.hpp +145 -0
- tpyc/_data/runtime/cpp/include/tpy/stdlib/time.hpp +62 -0
- tpyc/_data/runtime/cpp/include/tpy/system.hpp +121 -0
- tpyc/_data/runtime/cpp/include/tpy/throwable.hpp +55 -0
- tpyc/_data/runtime/cpp/include/tpy/tpy.hpp +156 -0
- tpyc/_data/runtime/cpp/include/tpy/type_name.hpp +77 -0
- tpyc/_data/runtime/cpp/include/tpy/type_traits.hpp +240 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_array_storage.hpp +250 -0
- tpyc/_data/runtime/cpp/include/tpy/uninit_heap_storage.hpp +277 -0
- tpyc/_data/runtime/cpp/include/tpy/varargs.hpp +174 -0
- tpyc/_data/runtime/cpp/include/tpy/variant_ref.hpp +118 -0
- tpyc/_data/runtime/cpp/src/stdlib/socket_impl.cpp +104 -0
- tpyc/_data/runtime/cpp/third_party/README.md +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/AUTHORS +36 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/CMakeLists.txt +1233 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/COPYING +5 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/ChangeLog +3097 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/HACKING +853 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/INSTALL +368 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/LICENCE +94 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NEWS +492 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/NON-AUTOTOOLS-BUILD +430 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/README +956 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/COPYING-CMAKE-SCRIPTS +22 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindEditline.cmake +16 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindPackageHandleStandardArgs.cmake +58 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/FindReadline.cmake +29 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config-version.cmake.in +15 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/cmake/pcre2-config.cmake.in +148 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/config-cmake.h.in +56 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-16.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-32.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-8.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/libpcre2-posix.pc.in +13 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/pcre2-config.in +121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.generic +483 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/config.h.in +460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.generic +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2.h.in +1010 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_auto_possess.c +1371 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chartables.c.dist +196 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_chkdint.c +96 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_compile.c +11001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_config.c +252 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_context.c +510 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_convert.c +1189 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dfa_match.c +4119 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_dftables.c +297 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_error.c +345 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_extuni.c +162 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_find_bracket.c +219 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_fuzzsupport.c +792 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_internal.h +2084 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_intmodedep.h +940 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_compile.c +14972 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_match.c +200 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_misc.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_neon_inc.h +354 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_simd_inc.h +2355 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_jit_test.c +2528 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_maketables.c +165 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match.c +7777 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_match_data.c +185 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_newline.c +243 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ord2utf.c +120 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_pattern_info.c +432 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_printint.c +886 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_script_run.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_serialize.c +286 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_string_utils.c +237 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_study.c +1915 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substitute.c +1009 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_substring.c +550 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_tables.c +234 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucd.c +5460 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucp.h +396 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_ucptables.c +1533 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_valid_utf.c +398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2_xclass.c +308 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2demo.c +497 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2grep.c +4606 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.c +425 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix.h +187 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2posix_test.c +209 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/pcre2test.c +9708 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorApple.c +137 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorCore.c +327 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorFreeBSD.c +89 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorPosix.c +62 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitExecAllocatorWindows.c +40 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorNetBSD.c +72 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitProtExecAllocatorPosix.c +172 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorPosix.c +141 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/allocator_src/sljitWXExecAllocatorWindows.c +102 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfig.h +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigCPU.h +188 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitConfigInternal.h +907 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.c +3561 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitLir.h +2466 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_32.c +4636 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_64.c +3491 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeARM_T2_32.c +4302 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeLOONGARCH_64.c +3765 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_32.c +472 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_64.c +387 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeMIPS_common.c +4259 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_32.c +485 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_64.c +719 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativePPC_common.c +3161 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_32.c +142 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_64.c +222 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeRISCV_common.c +3121 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeS390X.c +4526 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_32.c +1685 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_64.c +1398 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitNativeX86_common.c +5001 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitSerialize.c +516 -0
- tpyc/_data/runtime/cpp/third_party/pcre2/src/sljit/sljitUtils.c +344 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.sources.txt +54 -0
- tpyc/_data/runtime/cpp/third_party/pcre2.vendor.json +7 -0
- tpyc/build/__init__.py +7 -0
- tpyc/build/pcre2.py +122 -0
- tpyc/build/third_party.py +413 -0
- tpyc/cli.py +822 -0
- tpyc/codegen_cpp/__init__.py +18 -0
- tpyc/codegen_cpp/builtins.py +484 -0
- tpyc/codegen_cpp/context.py +2064 -0
- tpyc/codegen_cpp/expressions.py +5940 -0
- tpyc/codegen_cpp/functions.py +1913 -0
- tpyc/codegen_cpp/gen_async.py +3258 -0
- tpyc/codegen_cpp/gen_generators.py +657 -0
- tpyc/codegen_cpp/generator.py +2258 -0
- tpyc/codegen_cpp/match.py +1997 -0
- tpyc/codegen_cpp/param_const.py +172 -0
- tpyc/codegen_cpp/protocols.py +907 -0
- tpyc/codegen_cpp/records.py +1654 -0
- tpyc/codegen_cpp/resumable_cfg.py +1651 -0
- tpyc/codegen_cpp/statements.py +4963 -0
- tpyc/codegen_cpp/string_dispatch.py +76 -0
- tpyc/codegen_cpp/test_context.py +46 -0
- tpyc/codegen_cpp/test_param_const.py +113 -0
- tpyc/codegen_cpp/test_resumable_cfg.py +182 -0
- tpyc/codegen_cpp/type_resolution.py +53 -0
- tpyc/codegen_cpp/types.py +436 -0
- tpyc/codegen_cpp/variant_access.py +135 -0
- tpyc/coercions.py +749 -0
- tpyc/compilation_context.py +57 -0
- tpyc/compiler.py +3945 -0
- tpyc/cycle_detection.py +358 -0
- tpyc/diagnostics.py +135 -0
- tpyc/dump_types.py +353 -0
- tpyc/frontend_diagnostics.py +47 -0
- tpyc/frontend_ir/__init__.py +140 -0
- tpyc/frontend_ir/lower.py +1098 -0
- tpyc/frontend_ir/nodes.py +718 -0
- tpyc/frontend_ir/resolver_adapter.py +151 -0
- tpyc/frontend_plugin.py +209 -0
- tpyc/install_docs.py +81 -0
- tpyc/liveness.py +756 -0
- tpyc/macro_api.py +1724 -0
- tpyc/macro_loader.py +497 -0
- tpyc/module_names.py +64 -0
- tpyc/modules/__init__.py +31 -0
- tpyc/modules/defs.py +89 -0
- tpyc/modules/registry.py +36 -0
- tpyc/modules/resolver.py +192 -0
- tpyc/modules/type_resolution.py +629 -0
- tpyc/namespace.py +172 -0
- tpyc/parse/__init__.py +84 -0
- tpyc/parse/imports.py +490 -0
- tpyc/parse/nodes.py +1732 -0
- tpyc/parse/parser.py +4043 -0
- tpyc/parse/resolve_refs.py +466 -0
- tpyc/parse/type_resolver.py +1060 -0
- tpyc/prescan.py +254 -0
- tpyc/qnames.py +149 -0
- tpyc/repl.py +529 -0
- tpyc/repl_backends.py +848 -0
- tpyc/sema/__init__.py +21 -0
- tpyc/sema/analyzer.py +3625 -0
- tpyc/sema/bound_check.py +72 -0
- tpyc/sema/builder_trace.py +684 -0
- tpyc/sema/calls.py +5406 -0
- tpyc/sema/compatibility.py +2107 -0
- tpyc/sema/context.py +1243 -0
- tpyc/sema/expressions.py +3737 -0
- tpyc/sema/flow_facts.py +199 -0
- tpyc/sema/init_tracker.py +150 -0
- tpyc/sema/list_literals.py +69 -0
- tpyc/sema/literal_utils.py +27 -0
- tpyc/sema/local_deduction.py +1088 -0
- tpyc/sema/macros.py +179 -0
- tpyc/sema/match.py +1177 -0
- tpyc/sema/method_expansion.py +347 -0
- tpyc/sema/methods.py +2197 -0
- tpyc/sema/mutation_propagation.py +268 -0
- tpyc/sema/narrowing.py +857 -0
- tpyc/sema/numeric_lattice.py +160 -0
- tpyc/sema/operators.py +402 -0
- tpyc/sema/overloads.py +841 -0
- tpyc/sema/protocols.py +1209 -0
- tpyc/sema/reach_analysis.py +202 -0
- tpyc/sema/registration.py +3156 -0
- tpyc/sema/scope_tracker.py +193 -0
- tpyc/sema/statements.py +4426 -0
- tpyc/sema/type_ops.py +1879 -0
- tpyc/sema/value_range.py +181 -0
- tpyc/symbol_binding.py +259 -0
- tpyc/test_c3_mro.py +208 -0
- tpyc/test_cli_argv.py +52 -0
- tpyc/test_compiler.py +559 -0
- tpyc/test_contains_type_param.py +101 -0
- tpyc/test_cycle_detection.py +221 -0
- tpyc/test_dump_types.py +225 -0
- tpyc/test_install_docs.py +65 -0
- tpyc/test_local_cpp_form.py +135 -0
- tpyc/test_macro_loader.py +76 -0
- tpyc/test_method_expansion.py +254 -0
- tpyc/test_nominal_identity.py +182 -0
- tpyc/test_overloads.py +410 -0
- tpyc/test_parse.py +303 -0
- tpyc/test_parse_type_ref.py +506 -0
- tpyc/test_parse_version_info.py +58 -0
- tpyc/test_reach_analysis.py +72 -0
- tpyc/test_ref_type.py +216 -0
- tpyc/test_send_sync_substitution.py +276 -0
- tpyc/test_tuple_mutation_propagation.py +206 -0
- tpyc/test_type_def_registry.py +1729 -0
- tpyc/test_union_types.py +195 -0
- tpyc/type_def_registry.py +975 -0
- tpyc/typesys.py +5104 -0
|
@@ -0,0 +1,2258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython C++ Code Generator
|
|
3
|
+
|
|
4
|
+
Main orchestrator for generating C++ code from TurboPython AST.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from typing import Callable, TextIO, TYPE_CHECKING
|
|
10
|
+
import io
|
|
11
|
+
import sys as _sys
|
|
12
|
+
|
|
13
|
+
from ..typesys import TpyType, NominalType, UnionType, OwnType, PendingListType, PtrType, NoneType, VoidType, BIGINT, RecordInfo, ProtocolInfo, clear_codegen_state, register_native_cpp_name, register_union_alias, resolve_int_literals, is_void_like_type, bare_name
|
|
14
|
+
from ..compilation_context import require_current_compiler
|
|
15
|
+
from ..type_def_registry import type_def_of, is_enum_type, enum_info_of, protocol_info_of
|
|
16
|
+
from ..parse import TpyModule, TpyRecord, TpyFunction, TpyVarDecl, VarLinkage
|
|
17
|
+
from ..parse.nodes import TpyTupleUnpack, ModuleDirectives, TpyTry, TpyWith
|
|
18
|
+
from .resumable_cfg import (
|
|
19
|
+
ResumableShape, resumable_state,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from .context import CodeGenContext, CodeGenError, CodeGenOptions, module_to_cpp_namespace, module_has_cpp_namespace_override, qualified_cpp_name, qualify_native_name, escape_cpp_string, escape_cpp_char, escape_cpp_name
|
|
23
|
+
from .types import TypeResolver
|
|
24
|
+
from .protocols import ProtocolGenerator
|
|
25
|
+
from .builtins import BuiltinGenerator
|
|
26
|
+
from .expressions import ExpressionGenerator
|
|
27
|
+
from .statements import StatementGenerator
|
|
28
|
+
from .records import RecordGenerator
|
|
29
|
+
from .functions import FunctionGenerator
|
|
30
|
+
from .type_resolution import resolve_stmt_type_cascade
|
|
31
|
+
from .string_dispatch import find_best_discriminator, STRING_SWITCH_THRESHOLD
|
|
32
|
+
from ..symbol_binding import SymbolKind
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from ..sema import SemanticAnalyzer
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _emits_own_cpp(typ: NominalType) -> bool:
|
|
39
|
+
"""True if this NominalType's C++ emission bypasses its Python name --
|
|
40
|
+
either via a TypeDef cpp_formatter (builtins like list, str), via a
|
|
41
|
+
native_name mapping (@native records), or because it has no runtime
|
|
42
|
+
form (compile-time-only types like FStr). In those cases, an imported
|
|
43
|
+
`using PythonName = ...;` alias is dead code: the Python name never
|
|
44
|
+
appears in generated C++."""
|
|
45
|
+
td = type_def_of(typ)
|
|
46
|
+
if td is not None and (td.cpp_formatter is not None or td.is_compile_time_only):
|
|
47
|
+
return True
|
|
48
|
+
return typ.name in require_current_compiler().native_cpp_names
|
|
49
|
+
|
|
50
|
+
# Maps user-facing platform names to sys.platform prefixes (also in compiler.py)
|
|
51
|
+
_PLATFORM_MAP = {"windows": "win32", "linux": "linux", "macos": "darwin"}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _platform_matches(platform_filter: str | None) -> bool:
|
|
55
|
+
"""Check if a platform filter matches the current platform."""
|
|
56
|
+
if platform_filter is None:
|
|
57
|
+
return True
|
|
58
|
+
mapped = _PLATFORM_MAP.get(platform_filter.lower(), platform_filter)
|
|
59
|
+
return _sys.platform.startswith(mapped)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class _ProtocolDeps:
|
|
64
|
+
"""Dependency sets computed for protocol ordering."""
|
|
65
|
+
protocol_referenced_records: set[str] = field(default_factory=set)
|
|
66
|
+
bound_protocols: set[str] = field(default_factory=set)
|
|
67
|
+
bound_protocol_records: set[str] = field(default_factory=set)
|
|
68
|
+
early_definition_records: set[str] = field(default_factory=set)
|
|
69
|
+
prereq_protocols: set[str] = field(default_factory=set)
|
|
70
|
+
prereq_protocol_records: set[str] = field(default_factory=set)
|
|
71
|
+
module_record_names: set[str] = field(default_factory=set)
|
|
72
|
+
records_by_name: dict[str, TpyRecord] = field(default_factory=dict)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _references_nested_type(typ: TpyType) -> bool:
|
|
76
|
+
"""Check if a type references a nested type (dotted name)."""
|
|
77
|
+
if (isinstance(typ, NominalType) or is_enum_type(typ)) and "." in typ.name:
|
|
78
|
+
return True
|
|
79
|
+
return any(_references_nested_type(inner) for inner in typ.inner_types())
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _func_uses_nested_type(func: TpyFunction) -> bool:
|
|
83
|
+
"""Check if a function's signature references any nested type."""
|
|
84
|
+
for _, ptype in func.params:
|
|
85
|
+
if _references_nested_type(ptype):
|
|
86
|
+
return True
|
|
87
|
+
return _references_nested_type(func.return_type)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class CodeGenerator:
|
|
91
|
+
"""Generates C++ code from TurboPython AST."""
|
|
92
|
+
|
|
93
|
+
def __init__(self, analyzer: SemanticAnalyzer, options: CodeGenOptions | None = None):
|
|
94
|
+
self.analyzer = analyzer
|
|
95
|
+
self.options = options or CodeGenOptions()
|
|
96
|
+
|
|
97
|
+
# Create context (shared state)
|
|
98
|
+
self.ctx = CodeGenContext(
|
|
99
|
+
analyzer=analyzer,
|
|
100
|
+
options=self.options,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Create component generators (ordered by dependencies)
|
|
104
|
+
self.protocols = ProtocolGenerator(self.ctx)
|
|
105
|
+
self.types = TypeResolver(self.ctx, self.protocols)
|
|
106
|
+
self.builtins = BuiltinGenerator(self.ctx, self.types)
|
|
107
|
+
self.expressions = ExpressionGenerator(self.ctx, self.types, self.builtins, self.protocols)
|
|
108
|
+
self.statements = StatementGenerator(self.ctx, self.types, self.builtins, self.protocols, self.expressions)
|
|
109
|
+
self.functions = FunctionGenerator(self.ctx, self.types, self.protocols, self.statements)
|
|
110
|
+
self.records = RecordGenerator(self.ctx, self.types, self.protocols, self.expressions, self.functions)
|
|
111
|
+
|
|
112
|
+
# Generator codegen (must be created after wiring since it uses statements/functions)
|
|
113
|
+
from .gen_generators import GeneratorCodegen
|
|
114
|
+
self.gen_generators = GeneratorCodegen(
|
|
115
|
+
self.ctx, self.types, self.expressions, self.statements, self.functions)
|
|
116
|
+
self.records.gen_generators = self.gen_generators
|
|
117
|
+
|
|
118
|
+
# Async coroutine codegen (state-machine struct + Poll<T> poll())
|
|
119
|
+
from .gen_async import AsyncCoroCodegen
|
|
120
|
+
self.gen_async = AsyncCoroCodegen(
|
|
121
|
+
self.ctx, self.types, self.expressions, self.statements, self.functions)
|
|
122
|
+
self.records.gen_async = self.gen_async
|
|
123
|
+
# The resumable for-loop emit reuses the legacy strategy analysis
|
|
124
|
+
# (`_analyze_for_strategy`) so range/begin_end peepholes match.
|
|
125
|
+
self.gen_async.gen_generators = self.gen_generators
|
|
126
|
+
|
|
127
|
+
def generate(self, module: TpyModule, module_name: str = "generated",
|
|
128
|
+
is_entry_point: bool = True,
|
|
129
|
+
actual_user_modules: set[str] | None = None,
|
|
130
|
+
implicit_stdlib_modules: set[str] | None = None,
|
|
131
|
+
cycle_peers: 'frozenset[str] | None' = None) -> tuple[str, str]:
|
|
132
|
+
"""Generate C++ header and source files.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
module: The parsed TurboPython module AST.
|
|
136
|
+
module_name: Name for the generated files (used in #include).
|
|
137
|
+
is_entry_point: True if this is the entry point module (generates main()).
|
|
138
|
+
actual_user_modules: Set of module names that are actually user modules (have source files).
|
|
139
|
+
If None, uses module.user_module_imports (legacy behavior).
|
|
140
|
+
"""
|
|
141
|
+
self.ctx.module_name = module_name
|
|
142
|
+
self.ctx.source_lines = module.source_lines
|
|
143
|
+
self.ctx.cycle_peers = cycle_peers or frozenset()
|
|
144
|
+
# Filter out keyword stubs (@builtin_type classes / @builtin_decorator functions
|
|
145
|
+
# that exist only for import resolution -- no C++ code needed).
|
|
146
|
+
module.records = [
|
|
147
|
+
r for r in module.records
|
|
148
|
+
if not (ri := self.analyzer.registry.get_record(r.name)) or not ri.is_keyword_stub
|
|
149
|
+
]
|
|
150
|
+
module.functions = [
|
|
151
|
+
f for f in module.functions if not f.builtin_decorator_key
|
|
152
|
+
]
|
|
153
|
+
# Populate native C++ name mappings for this module's codegen.
|
|
154
|
+
# Must include both own records and imported records so NominalType.to_cpp()
|
|
155
|
+
# resolves correctly in all type positions (Ptr[Rect] -> SDL_Rect*, etc.)
|
|
156
|
+
clear_codegen_state()
|
|
157
|
+
native_cpp_names = require_current_compiler().native_cpp_names
|
|
158
|
+
for record in module.records:
|
|
159
|
+
record_info = self.analyzer.registry.get_record(record.name)
|
|
160
|
+
if record_info and record_info.is_native and record_info.native_name:
|
|
161
|
+
register_native_cpp_name(record.name, record_info.native_name)
|
|
162
|
+
# @native enums: register their canonical C++ qname so type-position
|
|
163
|
+
# renderings (`Optional[E]`, `list[E]`, function signatures) resolve
|
|
164
|
+
# to `ns::E` instead of `tpyapp::<module>::E`.
|
|
165
|
+
for enum in module.enums:
|
|
166
|
+
if not enum.is_native:
|
|
167
|
+
continue
|
|
168
|
+
enum_type = self.analyzer.registry.get_enum(enum.name)
|
|
169
|
+
if enum_type is None:
|
|
170
|
+
continue
|
|
171
|
+
einfo = enum_info_of(enum_type)
|
|
172
|
+
if einfo is not None and einfo.native_name:
|
|
173
|
+
register_native_cpp_name(enum.name, einfo.native_name)
|
|
174
|
+
# Register nested type names: "Outer.Inner" -> "Outer::Inner" for C++ qualified access.
|
|
175
|
+
# Write the map directly to avoid ensure_qualified adding "::" prefix
|
|
176
|
+
# (these are module-local types, not cross-module references).
|
|
177
|
+
for record in module.all_records():
|
|
178
|
+
if "." in record.name:
|
|
179
|
+
native_cpp_names[record.name] = record.name.replace(".", "::")
|
|
180
|
+
for enum in module.all_enums():
|
|
181
|
+
if "." in enum.name:
|
|
182
|
+
native_cpp_names[enum.name] = enum.name.replace(".", "::")
|
|
183
|
+
# Register C++ names for imported records/enums using their canonical
|
|
184
|
+
# (declaring-module) qname. NominalType.to_cpp() consults these when it
|
|
185
|
+
# has no qname-based formatter on hand. Iterate the full record/enum
|
|
186
|
+
# registries; imported_*_qualification filters out locals and builtins.
|
|
187
|
+
current_module = self.analyzer.ctx.module_name
|
|
188
|
+
for local_name, record_info in self.analyzer.registry.records.items():
|
|
189
|
+
if record_info.is_native and record_info.native_name:
|
|
190
|
+
register_native_cpp_name(local_name, record_info.native_name)
|
|
191
|
+
continue
|
|
192
|
+
if record_info.is_native:
|
|
193
|
+
continue
|
|
194
|
+
qual = self.analyzer.registry.imported_record_qualification(
|
|
195
|
+
local_name, current_module)
|
|
196
|
+
if qual is not None:
|
|
197
|
+
register_native_cpp_name(local_name, qualified_cpp_name(*qual))
|
|
198
|
+
# Cross-module @dynamic protocols (e.g. `Awaker` imported into
|
|
199
|
+
# `asyncio._executor` from `tpy.coro`) need the same `native_cpp_names`
|
|
200
|
+
# qualification as user records: `NominalType.to_cpp()` consults the
|
|
201
|
+
# map for protocol short names too. Locally-defined @dynamic protocols
|
|
202
|
+
# skip this registration so same-module references emit the bare name.
|
|
203
|
+
# Static protocols are excluded -- they monomorphize and never surface
|
|
204
|
+
# as a runtime C++ type.
|
|
205
|
+
for local_name, proto_info in self.analyzer.registry._protocols_by_local_name.items():
|
|
206
|
+
# @native + @dynamic protocols (e.g. `Throwable` which lives in
|
|
207
|
+
# runtime/cpp/include/tpy/throwable.hpp as ::tpy::Throwable) get
|
|
208
|
+
# their cpp_concept name as the C++ rendering, both for the
|
|
209
|
+
# defining module and for any module that imports them. The
|
|
210
|
+
# imported_protocol_qualification path would otherwise route
|
|
211
|
+
# through the codegen-emitted namespace (`tpystd::tpy::Throwable`)
|
|
212
|
+
# which doesn't exist for @native+@dynamic protocols.
|
|
213
|
+
if proto_info is not None and proto_info.cpp_concept and proto_info.is_dynamic:
|
|
214
|
+
register_native_cpp_name(local_name, proto_info.cpp_concept)
|
|
215
|
+
continue
|
|
216
|
+
qual = self.analyzer.registry.imported_protocol_qualification(
|
|
217
|
+
local_name, current_module)
|
|
218
|
+
if qual is not None:
|
|
219
|
+
register_native_cpp_name(local_name, qualified_cpp_name(*qual))
|
|
220
|
+
# `submod.X` (after `from pkg import submod`) doesn't import X by
|
|
221
|
+
# short name, so the loop above misses it. Walk each registered
|
|
222
|
+
# dep module's exports to register cross-module qualified names.
|
|
223
|
+
# Locally-defined record short names are excluded so a same-named
|
|
224
|
+
# peer record in a registered dep module never shadows the local
|
|
225
|
+
# `Foo`'s unqualified emission. Without this guard, a workspace
|
|
226
|
+
# that registers all modules into a shared dict (or any path that
|
|
227
|
+
# transitively pulls a peer module with a colliding short name)
|
|
228
|
+
# would misqualify the local record.
|
|
229
|
+
# Two cross-module record cases handled in one walk:
|
|
230
|
+
# (a) Plain user records imported from a peer module -- qualify
|
|
231
|
+
# through `record_info.module`.
|
|
232
|
+
# (b) `@builtin_type`-with-body records (Task and future
|
|
233
|
+
# siblings) whose stub claims a well-known qname like
|
|
234
|
+
# `tpy.Task` but whose body lives in a `# tpy: cpp_namespace`-
|
|
235
|
+
# tagged module (e.g. `asyncio._executor`). These would
|
|
236
|
+
# otherwise mis-qualify as `tpyapp::<module>::Name`, so the
|
|
237
|
+
# defining_module + cpp_namespace override path is required.
|
|
238
|
+
# Common skips (already registered, local shadow, @native rename)
|
|
239
|
+
# apply to both; the case split determines which module to qualify
|
|
240
|
+
# through.
|
|
241
|
+
local_short_record_names = {r.name for r in module.records}
|
|
242
|
+
for dep_module in self.analyzer.registry.modules.values():
|
|
243
|
+
if dep_module.name == current_module:
|
|
244
|
+
continue
|
|
245
|
+
for short, record_info in dep_module.records.items():
|
|
246
|
+
if short in native_cpp_names:
|
|
247
|
+
continue
|
|
248
|
+
if short in local_short_record_names:
|
|
249
|
+
continue
|
|
250
|
+
if record_info.is_native:
|
|
251
|
+
# @native rename handled above; skip whether or not
|
|
252
|
+
# native_name is set.
|
|
253
|
+
continue
|
|
254
|
+
if record_info.builtin_type_key:
|
|
255
|
+
# Case (b)
|
|
256
|
+
if record_info.is_keyword_stub:
|
|
257
|
+
continue
|
|
258
|
+
defining = record_info.defining_module
|
|
259
|
+
if not defining:
|
|
260
|
+
continue
|
|
261
|
+
if not module_has_cpp_namespace_override(defining):
|
|
262
|
+
continue
|
|
263
|
+
register_native_cpp_name(
|
|
264
|
+
short, qualified_cpp_name(defining, short))
|
|
265
|
+
else:
|
|
266
|
+
# Case (a)
|
|
267
|
+
if dep_module.is_builtin:
|
|
268
|
+
continue
|
|
269
|
+
if record_info.module is None or record_info.module == current_module:
|
|
270
|
+
continue
|
|
271
|
+
register_native_cpp_name(
|
|
272
|
+
short, qualified_cpp_name(record_info.module, short))
|
|
273
|
+
for local_name in list(self.analyzer.registry.enums.keys()):
|
|
274
|
+
qual = self.analyzer.registry.imported_enum_qualification(
|
|
275
|
+
local_name, current_module)
|
|
276
|
+
if qual is None:
|
|
277
|
+
continue
|
|
278
|
+
# @native imported enums: use their canonical C++ qname, not
|
|
279
|
+
# the module-qualified spelling.
|
|
280
|
+
enum_type = self.analyzer.registry.get_enum(local_name)
|
|
281
|
+
einfo = enum_info_of(enum_type) if enum_type is not None else None
|
|
282
|
+
if einfo is not None and einfo.is_native and einfo.native_name:
|
|
283
|
+
qualified = einfo.native_name
|
|
284
|
+
else:
|
|
285
|
+
qualified = qualified_cpp_name(*qual)
|
|
286
|
+
register_native_cpp_name(local_name, qualified)
|
|
287
|
+
# For aliased imports, also map the canonical name so references
|
|
288
|
+
# that go through NominalType.name resolve too.
|
|
289
|
+
original_name = qual[1]
|
|
290
|
+
if local_name != original_name:
|
|
291
|
+
register_native_cpp_name(original_name, qualified)
|
|
292
|
+
# Register native names from builtin type records without type_factory
|
|
293
|
+
# (e.g. TextIO -> tpy::TextFile, ValueError -> tpy::ValueError) so
|
|
294
|
+
# NominalType.to_cpp() resolves even when the type isn't explicitly imported.
|
|
295
|
+
# Skip names that shadow local record definitions in the current module.
|
|
296
|
+
local_record_names = {r.name for r in module.records}
|
|
297
|
+
for record_info in self.analyzer.registry.get_native_builtin_records():
|
|
298
|
+
if record_info.name not in local_record_names:
|
|
299
|
+
register_native_cpp_name(record_info.name, record_info.native_name)
|
|
300
|
+
# Register imported union type aliases so UnionType.to_cpp() can use
|
|
301
|
+
# the alias name instead of expanding to std::variant<...>. Skip
|
|
302
|
+
# recursive aliases here; the iter_imported_recursive_unions loop
|
|
303
|
+
# below registers them with their qualified C++ name so cross-module
|
|
304
|
+
# consumers emit the right type.
|
|
305
|
+
for local_name, (source_module, original_name) in self.analyzer.registry.imported_type_alias_info.items():
|
|
306
|
+
alias_info = self.analyzer.registry.get_type_alias_info(local_name)
|
|
307
|
+
if alias_info is not None and alias_info.type_params:
|
|
308
|
+
# Generic recursive aliases DO have a C++ identity (the wrapper
|
|
309
|
+
# template). Map the imported short name to the defining
|
|
310
|
+
# module's qualified name so RecursiveAliasInstanceType.to_cpp()
|
|
311
|
+
# emits `tpyapp::lib::Tree<...>` rather than a bare, undeclared
|
|
312
|
+
# `Tree<...>`. Generic non-recursive aliases are expanded at use
|
|
313
|
+
# sites and have no C++-level identity -- skip them.
|
|
314
|
+
src = self.analyzer.registry.modules.get(source_module)
|
|
315
|
+
if src is not None and original_name in src.recursive_union_names:
|
|
316
|
+
register_native_cpp_name(
|
|
317
|
+
local_name, qualified_cpp_name(source_module, original_name))
|
|
318
|
+
continue
|
|
319
|
+
alias_type = alias_info.body if alias_info is not None else None
|
|
320
|
+
if not isinstance(alias_type, UnionType):
|
|
321
|
+
continue
|
|
322
|
+
source_info = self.analyzer.registry.modules.get(source_module)
|
|
323
|
+
if source_info is not None and original_name in source_info.recursive_union_names:
|
|
324
|
+
continue
|
|
325
|
+
register_union_alias(alias_type.members, local_name)
|
|
326
|
+
# Filter user_module_imports to only include actual user modules (not builtins without user files)
|
|
327
|
+
if actual_user_modules is not None:
|
|
328
|
+
self.ctx.user_module_imports = {k: v for k, v in module.user_module_imports.items() if k in actual_user_modules}
|
|
329
|
+
self.ctx.all_user_modules = actual_user_modules
|
|
330
|
+
else:
|
|
331
|
+
self.ctx.user_module_imports = module.user_module_imports
|
|
332
|
+
self.ctx.all_user_modules = set(module.user_module_imports.keys())
|
|
333
|
+
self.ctx.implicit_stdlib_modules = implicit_stdlib_modules or set()
|
|
334
|
+
self.ctx.top_level_decls = dict(self.analyzer.ctx.top_level_decls)
|
|
335
|
+
self.ctx.macro_dep_modules = set(self.analyzer.ctx.macro_dep_modules)
|
|
336
|
+
# Recursive-union wrapper metadata is populated during sema
|
|
337
|
+
# (see analyzer._register_union_wrappers); codegen just reads
|
|
338
|
+
# it via UnionType.wrapper_info() / UnionType.needs_wrapper().
|
|
339
|
+
# Register recursive union aliases so record field rendering resolves
|
|
340
|
+
# e.g. Box[UnionType(Lit, BinOp)] -> Box<Expr>. Local aliases use the
|
|
341
|
+
# unqualified name; imported aliases are qualified to the defining
|
|
342
|
+
# module's namespace so cross-module consumers emit the right C++ type.
|
|
343
|
+
for name in module.recursive_union_names:
|
|
344
|
+
entry = module.type_aliases.get(name)
|
|
345
|
+
if entry is not None:
|
|
346
|
+
# Generic recursive aliases render via RecursiveAliasInstanceType
|
|
347
|
+
# (Tree<int>), not the member-tuple union_alias_names map; their
|
|
348
|
+
# members carry TypeParamRefs that must not pollute it.
|
|
349
|
+
if entry[2]:
|
|
350
|
+
continue
|
|
351
|
+
typ = entry[0]
|
|
352
|
+
if isinstance(typ, UnionType):
|
|
353
|
+
register_union_alias(typ.members, name)
|
|
354
|
+
for info in self.ctx.iter_imported_recursive_unions():
|
|
355
|
+
register_union_alias(
|
|
356
|
+
info.full_members,
|
|
357
|
+
qualified_cpp_name(info.origin, info.name),
|
|
358
|
+
)
|
|
359
|
+
hpp = io.StringIO()
|
|
360
|
+
cpp = io.StringIO()
|
|
361
|
+
|
|
362
|
+
from ..parse.nodes import FunctionLinkage
|
|
363
|
+
# Collect @native (C++ import) functions for out-of-namespace handling
|
|
364
|
+
native_funcs = [f for f in module.functions if f.linkage == FunctionLinkage.NATIVE]
|
|
365
|
+
|
|
366
|
+
# Separate global declarations from other top-level statements early
|
|
367
|
+
# (needed for extern declarations in header)
|
|
368
|
+
# Track seen names and types to handle re-declarations (z = 0; z = 5; -> one global, one assignment)
|
|
369
|
+
global_decls = []
|
|
370
|
+
final_decls: list[TpyVarDecl] = []
|
|
371
|
+
native_globals: list[TpyVarDecl] = []
|
|
372
|
+
seen_globals: dict[str, TpyType | None] = {}
|
|
373
|
+
for stmt in module.top_level_stmts:
|
|
374
|
+
if isinstance(stmt, TpyVarDecl):
|
|
375
|
+
if stmt.name not in seen_globals:
|
|
376
|
+
# Store the type for this global
|
|
377
|
+
var_type = resolve_stmt_type_cascade(stmt, self.analyzer, self.types)
|
|
378
|
+
if isinstance(var_type, OwnType):
|
|
379
|
+
var_type = var_type.wrapped
|
|
380
|
+
var_type = resolve_int_literals(var_type, self.analyzer.ctx.default_int_for_literal)
|
|
381
|
+
seen_globals[stmt.name] = var_type
|
|
382
|
+
if stmt.linkage != VarLinkage.DEFAULT:
|
|
383
|
+
native_globals.append(stmt)
|
|
384
|
+
elif stmt.is_final:
|
|
385
|
+
final_decls.append(stmt)
|
|
386
|
+
else:
|
|
387
|
+
global_decls.append(stmt)
|
|
388
|
+
elif isinstance(stmt, TpyTupleUnpack):
|
|
389
|
+
for i, name in enumerate(stmt.targets):
|
|
390
|
+
if name is None or name in seen_globals:
|
|
391
|
+
continue
|
|
392
|
+
var_type = stmt.target_types[i]
|
|
393
|
+
if isinstance(var_type, OwnType):
|
|
394
|
+
var_type = var_type.wrapped
|
|
395
|
+
seen_globals[name] = var_type
|
|
396
|
+
synthetic = TpyVarDecl(
|
|
397
|
+
name=name, type=var_type, init=None, loc=stmt.loc)
|
|
398
|
+
global_decls.append(synthetic)
|
|
399
|
+
|
|
400
|
+
# Track native global name mappings (Python name -> C/C++ name)
|
|
401
|
+
self.ctx.native_global_names = {
|
|
402
|
+
stmt.name: (stmt.native_name or stmt.name)
|
|
403
|
+
for stmt in native_globals
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if module.directives.native_module:
|
|
407
|
+
# Native modules are declaration-only -- no .hpp or .cpp generated.
|
|
408
|
+
# Their # tpy: include() directives are propagated to importing modules.
|
|
409
|
+
return "", ""
|
|
410
|
+
|
|
411
|
+
self._write_header_preamble(hpp, native_funcs, native_globals, directives=module.directives)
|
|
412
|
+
|
|
413
|
+
self._write_source_preamble(cpp, module)
|
|
414
|
+
|
|
415
|
+
# Store global names for use in expression generation (method/field access)
|
|
416
|
+
self.ctx.global_names = set(seen_globals.keys())
|
|
417
|
+
# Track Final globals (constexpr/const at namespace scope)
|
|
418
|
+
self.ctx.final_globals = {stmt.name for stmt in final_decls}
|
|
419
|
+
# Classify globals: non-value-type -> pointer globals (T*)
|
|
420
|
+
# Exclude native globals and final globals
|
|
421
|
+
self.ctx.pointer_globals = {
|
|
422
|
+
name for name, typ in seen_globals.items()
|
|
423
|
+
if typ and not typ.is_value_type()
|
|
424
|
+
and not typ.needs_wrapper()
|
|
425
|
+
and name not in self.ctx.native_global_names
|
|
426
|
+
and name not in self.ctx.final_globals
|
|
427
|
+
}
|
|
428
|
+
# Also include imported non-value-type globals
|
|
429
|
+
for name, cell in (self.analyzer.ctx.module_attributes or {}).items():
|
|
430
|
+
bd = cell.binding
|
|
431
|
+
if bd.kind != SymbolKind.VARIABLE or bd.defining_module is None:
|
|
432
|
+
continue
|
|
433
|
+
binding = self.ctx.analyzer.global_ns.lookup_local(name)
|
|
434
|
+
if binding and binding.type and not binding.type.is_value_type() and not binding.type.needs_wrapper():
|
|
435
|
+
self.ctx.pointer_globals.add(name)
|
|
436
|
+
# Generate protocol ordering and forward declarations
|
|
437
|
+
self._generate_protocol_ordering(hpp, module, global_decls, final_decls, seen_globals)
|
|
438
|
+
|
|
439
|
+
# Generate global definitions in source (before functions)
|
|
440
|
+
for stmt in global_decls:
|
|
441
|
+
self.functions.gen_global_decl(cpp, stmt)
|
|
442
|
+
# Final globals: constexpr in header, const in source (BigInt only)
|
|
443
|
+
for stmt in final_decls:
|
|
444
|
+
self.functions.gen_final_global_source(cpp, stmt)
|
|
445
|
+
cpp.write("\n")
|
|
446
|
+
|
|
447
|
+
# Generate function definitions (skip template functions -- defined in header)
|
|
448
|
+
for func in module.functions:
|
|
449
|
+
if func.skip_codegen:
|
|
450
|
+
continue
|
|
451
|
+
if func.is_generator:
|
|
452
|
+
if self._resumable_generator_eligible(func):
|
|
453
|
+
# Generator lowered onto the resumable frame. Non-template
|
|
454
|
+
# generators emit the __next__ body + factory here in the
|
|
455
|
+
# .cpp; templated ones (generic / protocol-typed params)
|
|
456
|
+
# emit inline in the header instead (see the
|
|
457
|
+
# struct-definition pass), so skip the .cpp emission for
|
|
458
|
+
# them.
|
|
459
|
+
if not self.gen_async._is_templated_coro(func):
|
|
460
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
461
|
+
self.gen_async.gen_coro_poll_def(cpp, func)
|
|
462
|
+
cpp.write("\n")
|
|
463
|
+
self.gen_async.gen_coro_finally_top_def(cpp, func)
|
|
464
|
+
cpp.write("\n")
|
|
465
|
+
self.gen_async.gen_factory(cpp, func)
|
|
466
|
+
cpp.write("\n")
|
|
467
|
+
# Simple generators are defined inline in the header
|
|
468
|
+
continue
|
|
469
|
+
if func.is_async:
|
|
470
|
+
# Templates are emitted inline in the header (same rule as
|
|
471
|
+
# template functions). For non-template async coros, emit
|
|
472
|
+
# poll() body, optional __finally_top(), and the factory
|
|
473
|
+
# function in the .cpp. `_is_templated_coro` (not bare
|
|
474
|
+
# type_params) so a protocol-typed param -- which also makes
|
|
475
|
+
# the struct a template -- routes to the header, not an
|
|
476
|
+
# out-of-line .cpp body that would fail to link.
|
|
477
|
+
if not self.gen_async._is_templated_coro(func):
|
|
478
|
+
self.gen_async.gen_coro_poll_def(cpp, func)
|
|
479
|
+
cpp.write("\n")
|
|
480
|
+
self.gen_async.gen_coro_finally_top_def(cpp, func)
|
|
481
|
+
cpp.write("\n")
|
|
482
|
+
self.gen_async.gen_factory(cpp, func)
|
|
483
|
+
cpp.write("\n")
|
|
484
|
+
continue
|
|
485
|
+
if self.functions.is_template_function(func):
|
|
486
|
+
continue
|
|
487
|
+
self.functions.gen_function_def(cpp, func)
|
|
488
|
+
cpp.write("\n")
|
|
489
|
+
|
|
490
|
+
# Method generator __next__() / async __poll__() definitions
|
|
491
|
+
for record in module.all_records():
|
|
492
|
+
for method in record.methods:
|
|
493
|
+
if method.is_generator and not self.gen_generators.is_simple_generator(method):
|
|
494
|
+
# Eligibility is True-or-raises for a non-simple generator
|
|
495
|
+
# method; the call also builds + caches the CFG. Non-template
|
|
496
|
+
# methods emit the __next__ body + finally-top in the .cpp
|
|
497
|
+
# (templated ones emit inline next to the struct in the
|
|
498
|
+
# .hpp, see below).
|
|
499
|
+
if (self._resumable_generator_eligible(method)
|
|
500
|
+
and not self.gen_async._is_templated_coro(method, record.name)):
|
|
501
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
502
|
+
self.gen_async.gen_coro_poll_def(
|
|
503
|
+
cpp, method, record_name=record.name)
|
|
504
|
+
cpp.write("\n")
|
|
505
|
+
self.gen_async.gen_coro_finally_top_def(
|
|
506
|
+
cpp, method, record_name=record.name)
|
|
507
|
+
cpp.write("\n")
|
|
508
|
+
elif method.is_async and not self.gen_async._is_templated_coro(method, record.name):
|
|
509
|
+
# Non-template async methods: poll body lands in .cpp.
|
|
510
|
+
# Template async methods (type params OR a protocol-typed
|
|
511
|
+
# param) have their poll body emitted inline next to the
|
|
512
|
+
# struct in the .hpp (see above).
|
|
513
|
+
self.gen_async.gen_coro_poll_def(
|
|
514
|
+
cpp, method, record_name=record.name)
|
|
515
|
+
cpp.write("\n")
|
|
516
|
+
self.gen_async.gen_coro_finally_top_def(
|
|
517
|
+
cpp, method, record_name=record.name)
|
|
518
|
+
cpp.write("\n")
|
|
519
|
+
|
|
520
|
+
# Non-trivial method bodies live in the .cpp (matches free-function
|
|
521
|
+
# placement). Trivial bodies stay in the .hpp -- emitted earlier by
|
|
522
|
+
# the matching `mode="def_hpp"` pass in `_generate_protocol_ordering`.
|
|
523
|
+
for record in self.records.sort_records_by_inheritance(module.records):
|
|
524
|
+
self.records.gen_record_method_defs(cpp, record, mode="def_cpp")
|
|
525
|
+
|
|
526
|
+
# Generate module init function and main()
|
|
527
|
+
# Always generate __tpy_init for global initialization (Python semantics)
|
|
528
|
+
# Pass ALL top-level statements to init function (including globals)
|
|
529
|
+
# Note: has_user_main=False because users should call main() explicitly at top level,
|
|
530
|
+
# either as `main()` or `if __name__ == "__main__": main()`
|
|
531
|
+
self.functions.gen_module_init_decl(hpp)
|
|
532
|
+
# Pass module name for __name__ variable initialization
|
|
533
|
+
tpy_module_name = self.analyzer.ctx.module_name
|
|
534
|
+
# Imports are now included in top_level_stmts as TpyImport nodes
|
|
535
|
+
# They get emitted as __tpy_init() calls in statement order (Python semantics)
|
|
536
|
+
# Exclude Final globals from init pre-seeding (they live at namespace scope)
|
|
537
|
+
init_globals = {k: v for k, v in seen_globals.items() if k not in self.ctx.final_globals}
|
|
538
|
+
self.functions.gen_module_init(cpp, module.top_level_stmts, init_globals,
|
|
539
|
+
has_user_main=False, module_name=tpy_module_name)
|
|
540
|
+
# Only generate C++ main() for entry point module
|
|
541
|
+
if is_entry_point:
|
|
542
|
+
self.functions.gen_main(cpp, no_main=self.options.no_main)
|
|
543
|
+
else:
|
|
544
|
+
# For non-entry-point modules, just close the namespace
|
|
545
|
+
self.functions.gen_namespace_close(cpp)
|
|
546
|
+
|
|
547
|
+
self._write_header_epilogue(hpp)
|
|
548
|
+
|
|
549
|
+
return hpp.getvalue(), cpp.getvalue()
|
|
550
|
+
|
|
551
|
+
def _resumable_generator_eligible(self, func: "TpyFunction") -> bool:
|
|
552
|
+
"""Gate for routing a non-simple generator (free function OR method)
|
|
553
|
+
through the resumable state-machine emitter (`gen_async`). Returns
|
|
554
|
+
False only for the simple-peephole shape (which is emitted inline);
|
|
555
|
+
for any other generator it returns True or raises a clean diagnostic
|
|
556
|
+
-- there is no longer a legacy struct path to fall back to.
|
|
557
|
+
Protocol-typed params are rejected (the for-loop frame field would be
|
|
558
|
+
typed against the abstract concept, not the deduced template param);
|
|
559
|
+
generic generators (`[T]`) are eligible (templated struct + `__next__`
|
|
560
|
+
+ factory emit inline in the header). Parameters are captured via the
|
|
561
|
+
async `_classify_params` machinery (value by value, str as
|
|
562
|
+
string_view, reference types by `T&`, `Own[T]` moved in, pointer-repr
|
|
563
|
+
`Optional` as `T*`). Tuple yields use the borrow-form slot via
|
|
564
|
+
`gen_generators._iter_slot_for_yield`.
|
|
565
|
+
|
|
566
|
+
The decision is memoized on the func: the gate is consulted from
|
|
567
|
+
several orchestration passes, and building the CFG per call would be
|
|
568
|
+
wasteful.
|
|
569
|
+
"""
|
|
570
|
+
state = resumable_state(func)
|
|
571
|
+
if state.gen_eligible is not None:
|
|
572
|
+
return state.gen_eligible
|
|
573
|
+
result = self._compute_resumable_generator_eligible(func)
|
|
574
|
+
state.gen_eligible = result
|
|
575
|
+
return result
|
|
576
|
+
|
|
577
|
+
def _compute_resumable_generator_eligible(self, func: "TpyFunction") -> bool:
|
|
578
|
+
# Generic generators (explicit `[T]` type params) are eligible: the
|
|
579
|
+
# resumable emitter is template-aware (struct + __next__ + factory
|
|
580
|
+
# emitted inline in the header for templated coros, exactly like
|
|
581
|
+
# generic async defs). For a non-simple generator this returns True
|
|
582
|
+
# or raises a clean diagnostic -- it never returns False (there is
|
|
583
|
+
# no longer a legacy struct path to fall back to).
|
|
584
|
+
if not func.is_generator:
|
|
585
|
+
return False
|
|
586
|
+
# Simple single-yield generators use the lightweight lambda peephole
|
|
587
|
+
# (`gen_simple_generator_inline`); everything else is a resumable
|
|
588
|
+
# frame.
|
|
589
|
+
if self.gen_generators.is_simple_generator(func):
|
|
590
|
+
return False
|
|
591
|
+
if func.generator_yield_type is None:
|
|
592
|
+
return False
|
|
593
|
+
# Protocol-typed params (`def gen(it: Iterable[T])`) make the struct
|
|
594
|
+
# a template (`T_it`), but the resumable for-loop frame field would
|
|
595
|
+
# be typed against the abstract protocol (`Iterable<T>`, a C++
|
|
596
|
+
# concept) instead of the deduced template param -- an ill-formed
|
|
597
|
+
# field decl. That substitution is a separate feature (TODO.md
|
|
598
|
+
# "Generator: protocol-typed params"). (Explicit `[T]` type params
|
|
599
|
+
# have no such issue and stay eligible.)
|
|
600
|
+
if self.protocols.get_all_protocol_params(func.params):
|
|
601
|
+
raise CodeGenError(
|
|
602
|
+
"generators with a protocol-typed parameter are not yet "
|
|
603
|
+
"supported",
|
|
604
|
+
loc=func.loc,
|
|
605
|
+
)
|
|
606
|
+
# Backstop: build (and cache) the CFG, which raises a clean located
|
|
607
|
+
# CodeGenError for any shape the resumable lowering can't handle, so a
|
|
608
|
+
# residual unsupported shape surfaces as a diagnostic rather than a
|
|
609
|
+
# silent miscompile.
|
|
610
|
+
self.gen_async._build_resumable_cfg(func)
|
|
611
|
+
return True
|
|
612
|
+
|
|
613
|
+
def _generate_protocol_ordering(self, hpp: TextIO, module: TpyModule,
|
|
614
|
+
global_decls: list, final_decls: list,
|
|
615
|
+
seen_globals: dict) -> None:
|
|
616
|
+
"""Generate protocols, forward declarations, and records in proper order.
|
|
617
|
+
|
|
618
|
+
C++ requires forward declarations and concepts to be defined before use.
|
|
619
|
+
This method handles the complex ordering requirements.
|
|
620
|
+
"""
|
|
621
|
+
deps = self._collect_protocol_deps(module)
|
|
622
|
+
# Emit before forward decls/concepts so inline references to imported
|
|
623
|
+
# names are in scope (e.g. struct member initializers using `Rc<T>`).
|
|
624
|
+
self._gen_reexport_using_decls(hpp)
|
|
625
|
+
self._generate_forward_decls_and_concepts(hpp, module, deps)
|
|
626
|
+
self._generate_definitions_and_reexports(hpp, module, global_decls, final_decls, seen_globals, deps)
|
|
627
|
+
|
|
628
|
+
def _is_native_record(self, record_name: str) -> bool:
|
|
629
|
+
"""Check if a record is a native import."""
|
|
630
|
+
record_info = self.analyzer.registry.get_record(record_name)
|
|
631
|
+
return record_info is not None and record_info.is_native
|
|
632
|
+
|
|
633
|
+
def _collect_protocol_deps(self, module: TpyModule) -> _ProtocolDeps:
|
|
634
|
+
"""Collect all dependency sets needed for protocol ordering."""
|
|
635
|
+
# Exclude native records -- they don't generate C++ structs
|
|
636
|
+
non_native = [r for r in module.records if not self._is_native_record(r.name)]
|
|
637
|
+
deps = _ProtocolDeps(
|
|
638
|
+
module_record_names={r.name for r in non_native},
|
|
639
|
+
records_by_name={r.name: r for r in non_native},
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Records referenced in protocol signatures
|
|
643
|
+
for protocol in module.protocols:
|
|
644
|
+
for method_sig in protocol.methods:
|
|
645
|
+
self.protocols.collect_record_types_from_type(method_sig.return_type, deps.protocol_referenced_records)
|
|
646
|
+
for _, param_type in method_sig.params:
|
|
647
|
+
self.protocols.collect_record_types_from_type(param_type, deps.protocol_referenced_records)
|
|
648
|
+
for _, field_type in protocol.fields:
|
|
649
|
+
self.protocols.collect_record_types_from_type(field_type, deps.protocol_referenced_records)
|
|
650
|
+
|
|
651
|
+
# User-defined protocols used as bounds on protocol-referenced records
|
|
652
|
+
for record_name in deps.protocol_referenced_records:
|
|
653
|
+
if record_name in deps.module_record_names:
|
|
654
|
+
record = deps.records_by_name[record_name]
|
|
655
|
+
for bound in record.type_param_bounds.values():
|
|
656
|
+
proto_info = protocol_info_of(bound)
|
|
657
|
+
if proto_info is None or proto_info.cpp_concept is None:
|
|
658
|
+
deps.bound_protocols.add(bound.name)
|
|
659
|
+
|
|
660
|
+
# Records referenced by bound protocols
|
|
661
|
+
for protocol in module.protocols:
|
|
662
|
+
if protocol.name in deps.bound_protocols:
|
|
663
|
+
for method_sig in protocol.methods:
|
|
664
|
+
self.protocols.collect_record_types_from_type(method_sig.return_type, deps.bound_protocol_records)
|
|
665
|
+
for _, param_type in method_sig.params:
|
|
666
|
+
self.protocols.collect_record_types_from_type(param_type, deps.bound_protocol_records)
|
|
667
|
+
for _, field_type in protocol.fields:
|
|
668
|
+
self.protocols.collect_record_types_from_type(field_type, deps.bound_protocol_records)
|
|
669
|
+
|
|
670
|
+
# Records used as type args to bounded records in protocol signatures
|
|
671
|
+
for protocol in module.protocols:
|
|
672
|
+
if protocol.name in deps.bound_protocols:
|
|
673
|
+
continue
|
|
674
|
+
for method_sig in protocol.methods:
|
|
675
|
+
self.protocols.collect_type_args_of_bounded_records(
|
|
676
|
+
method_sig.return_type, deps.records_by_name, deps.early_definition_records
|
|
677
|
+
)
|
|
678
|
+
for _, param_type in method_sig.params:
|
|
679
|
+
self.protocols.collect_type_args_of_bounded_records(
|
|
680
|
+
param_type, deps.records_by_name, deps.early_definition_records
|
|
681
|
+
)
|
|
682
|
+
for _, field_type in protocol.fields:
|
|
683
|
+
self.protocols.collect_type_args_of_bounded_records(
|
|
684
|
+
field_type, deps.records_by_name, deps.early_definition_records
|
|
685
|
+
)
|
|
686
|
+
|
|
687
|
+
# Prereq protocols: user-defined protocols that are bounds on bound_protocol_records
|
|
688
|
+
for record_name in deps.bound_protocol_records:
|
|
689
|
+
if record_name in deps.module_record_names:
|
|
690
|
+
record = deps.records_by_name[record_name]
|
|
691
|
+
for bound in record.type_param_bounds.values():
|
|
692
|
+
proto_info = protocol_info_of(bound)
|
|
693
|
+
if proto_info is None or proto_info.cpp_concept is None:
|
|
694
|
+
deps.prereq_protocols.add(bound.name)
|
|
695
|
+
|
|
696
|
+
# Records referenced by prereq protocols
|
|
697
|
+
for protocol in module.protocols:
|
|
698
|
+
if protocol.name in deps.prereq_protocols:
|
|
699
|
+
for method_sig in protocol.methods:
|
|
700
|
+
self.protocols.collect_record_types_from_type(method_sig.return_type, deps.prereq_protocol_records)
|
|
701
|
+
for _, param_type in method_sig.params:
|
|
702
|
+
self.protocols.collect_record_types_from_type(param_type, deps.prereq_protocol_records)
|
|
703
|
+
for _, field_type in protocol.fields:
|
|
704
|
+
self.protocols.collect_record_types_from_type(field_type, deps.prereq_protocol_records)
|
|
705
|
+
|
|
706
|
+
return deps
|
|
707
|
+
|
|
708
|
+
def _emit_hash_specialization(self, hpp: TextIO, record: TpyRecord) -> None:
|
|
709
|
+
"""Emit std::hash specialization for a hashable record.
|
|
710
|
+
|
|
711
|
+
Called immediately after the record's struct definition so that
|
|
712
|
+
subsequent records can use it as a set/dict-key element type.
|
|
713
|
+
`@native`-renamed records use `record_info.native_name` as the
|
|
714
|
+
qualified target (mirrors `_emit_value_type_spec`).
|
|
715
|
+
"""
|
|
716
|
+
info = self.analyzer.registry.get_record(record.name)
|
|
717
|
+
if not info or "__hash__" not in info.methods:
|
|
718
|
+
return
|
|
719
|
+
if record.type_params or record.builtin_type_key:
|
|
720
|
+
return
|
|
721
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
722
|
+
cpp_name = (qualify_native_name(info.native_name)
|
|
723
|
+
if info.native_name
|
|
724
|
+
else qualified_cpp_name(self.ctx.module_name, record.name))
|
|
725
|
+
hpp.write(f"}} // namespace {ns}\n\n")
|
|
726
|
+
hpp.write(f"template<> struct std::hash<{cpp_name}> {{\n")
|
|
727
|
+
hpp.write(f" size_t operator()(const {cpp_name}& val) const noexcept {{\n")
|
|
728
|
+
hpp.write(f" return static_cast<size_t>(::tpy::__hash__(val));\n")
|
|
729
|
+
hpp.write(f" }}\n")
|
|
730
|
+
hpp.write(f"}};\n")
|
|
731
|
+
hpp.write(f"\nnamespace {ns} {{\n\n")
|
|
732
|
+
|
|
733
|
+
def _emit_nested_hash_specializations(self, hpp: TextIO, record: 'TpyRecord') -> None:
|
|
734
|
+
"""Emit std::hash specializations for nested records (recursively).
|
|
735
|
+
|
|
736
|
+
Skips `@native` nested records -- those are emitted by the
|
|
737
|
+
dedicated native-records pass in `_generate_definitions_and_reexports`.
|
|
738
|
+
Symmetric with `_emit_nested_value_type_specs`.
|
|
739
|
+
"""
|
|
740
|
+
for nested_rec in record.nested_records:
|
|
741
|
+
if self._is_native_record(nested_rec.name):
|
|
742
|
+
continue
|
|
743
|
+
self._emit_hash_specialization(hpp, nested_rec)
|
|
744
|
+
self._emit_nested_hash_specializations(hpp, nested_rec)
|
|
745
|
+
|
|
746
|
+
def _emit_value_type_spec(self, hpp: TextIO, record: TpyRecord) -> None:
|
|
747
|
+
"""Emit `tpy::is_value_type<T>` spec for a ValueType record.
|
|
748
|
+
|
|
749
|
+
Called immediately after the record's struct definition so any
|
|
750
|
+
subsequent inline method body / templated call instantiating on
|
|
751
|
+
the record sees the spec. `@native`-renamed records use
|
|
752
|
+
`record_info.native_name` as the qualified C++ type (the rename
|
|
753
|
+
target lives outside the module's namespace).
|
|
754
|
+
|
|
755
|
+
Note: no `builtin_type_key` guard, asymmetric with
|
|
756
|
+
`_emit_hash_specialization`. `@builtin_type` ValueType records
|
|
757
|
+
(e.g. `Waker`) rely on this emission today -- there's no
|
|
758
|
+
hand-written spec in `runtime/cpp/include/tpy/type_traits.hpp`
|
|
759
|
+
for them, so the codegen path is load-bearing. Revisit if a
|
|
760
|
+
future `@builtin_type` + `@native` ValueType adds a hand-written
|
|
761
|
+
spec; the resulting duplicate `template<>` would be a hard C++
|
|
762
|
+
error pointing here.
|
|
763
|
+
"""
|
|
764
|
+
record_info = self.analyzer.registry.get_record(record.name)
|
|
765
|
+
if not record_info or not record_info.is_value_type:
|
|
766
|
+
return
|
|
767
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
768
|
+
base_cpp_name = (qualify_native_name(record_info.native_name)
|
|
769
|
+
if record_info.native_name
|
|
770
|
+
else qualified_cpp_name(self.ctx.module_name, record.name))
|
|
771
|
+
hpp.write(f"}} // namespace {ns}\n\n")
|
|
772
|
+
if record.type_params:
|
|
773
|
+
tparams_decl = ", ".join(
|
|
774
|
+
f"std::size_t {tp}" if (i < len(record_info.type_param_kinds)
|
|
775
|
+
and record_info.type_param_kinds[i].name == "INT")
|
|
776
|
+
else f"typename {tp}"
|
|
777
|
+
for i, tp in enumerate(record.type_params)
|
|
778
|
+
)
|
|
779
|
+
tparams_use = ", ".join(record.type_params)
|
|
780
|
+
hpp.write(f"template<{tparams_decl}> struct tpy::is_value_type<{base_cpp_name}<{tparams_use}>> : std::true_type {{}};\n")
|
|
781
|
+
else:
|
|
782
|
+
hpp.write(f"template<> struct tpy::is_value_type<{base_cpp_name}> : std::true_type {{}};\n")
|
|
783
|
+
hpp.write(f"\nnamespace {ns} {{\n\n")
|
|
784
|
+
|
|
785
|
+
def _emit_nested_value_type_specs(self, hpp: TextIO, record: 'TpyRecord') -> None:
|
|
786
|
+
"""Emit value-type specs for nested records (recursively).
|
|
787
|
+
|
|
788
|
+
Skips `@native` nested records -- those are emitted by the
|
|
789
|
+
dedicated native-records pass in `_generate_definitions_and_reexports`.
|
|
790
|
+
Emitting here too would produce a duplicate `template<>` (hard C++
|
|
791
|
+
error). Parser today rejects `@native` nested in `@native` but
|
|
792
|
+
permits `@native` nested in a non-native outer, so the duplicate
|
|
793
|
+
path is reachable without this guard.
|
|
794
|
+
"""
|
|
795
|
+
for nested_rec in record.nested_records:
|
|
796
|
+
if self._is_native_record(nested_rec.name):
|
|
797
|
+
continue
|
|
798
|
+
self._emit_value_type_spec(hpp, nested_rec)
|
|
799
|
+
self._emit_nested_value_type_specs(hpp, nested_rec)
|
|
800
|
+
|
|
801
|
+
def _emit_concept_and_dynamic(self, hpp: TextIO, protocol: 'TpyProtocol') -> None:
|
|
802
|
+
"""Emit concept for a protocol, plus base class if @dynamic.
|
|
803
|
+
|
|
804
|
+
Adapter specializations (::tpy::Adapter, ::tpy::RefAdapter) are emitted
|
|
805
|
+
separately at global scope via _gen_dynamic_adapter_specs().
|
|
806
|
+
"""
|
|
807
|
+
from ..parse import TpyProtocol as _TP
|
|
808
|
+
emitted = self.protocols.gen_concept_decl(hpp, protocol)
|
|
809
|
+
if not emitted:
|
|
810
|
+
return
|
|
811
|
+
if protocol.is_dynamic:
|
|
812
|
+
hpp.write("\n")
|
|
813
|
+
self.protocols.gen_dynamic_base_class(hpp, protocol)
|
|
814
|
+
hpp.write("\n")
|
|
815
|
+
|
|
816
|
+
def _generate_forward_decls_and_concepts(
|
|
817
|
+
self, hpp: TextIO, module: TpyModule, deps: _ProtocolDeps
|
|
818
|
+
) -> None:
|
|
819
|
+
"""Generate forward declarations for prereq/bound/protocol-referenced records and concepts."""
|
|
820
|
+
# A protocol method signature can reference a recursive-union wrapper
|
|
821
|
+
# (`def m(self, t: Tree[Int32])` / `def m(self, e: Expr)`), which
|
|
822
|
+
# renders inside the concept body as `Tree<int32_t>` / `Expr` -- so the
|
|
823
|
+
# wrapper must be forward-declared before any concept. Covers both
|
|
824
|
+
# generic (template) and non-generic wrappers. Only needed when
|
|
825
|
+
# protocols exist; otherwise the fwd-decl before records (in
|
|
826
|
+
# _generate_definitions_and_reexports) suffices, and skipping here
|
|
827
|
+
# avoids churning protocol-free recursive-alias snapshots.
|
|
828
|
+
emitted_wrapper_fwd = False
|
|
829
|
+
if module.protocols:
|
|
830
|
+
for name, entry in module.type_aliases.items():
|
|
831
|
+
if name in module.recursive_union_names:
|
|
832
|
+
if entry[2]:
|
|
833
|
+
header = self.protocols.gen_record_template_header(
|
|
834
|
+
entry[2], {}, entry[3])
|
|
835
|
+
hpp.write(f"{header} struct {name};\n")
|
|
836
|
+
else:
|
|
837
|
+
hpp.write(f"struct {name};\n")
|
|
838
|
+
emitted_wrapper_fwd = True
|
|
839
|
+
if emitted_wrapper_fwd:
|
|
840
|
+
hpp.write("\n")
|
|
841
|
+
|
|
842
|
+
# Forward declare records referenced by prereq protocols (unconstrained)
|
|
843
|
+
for record_name in sorted(deps.prereq_protocol_records):
|
|
844
|
+
if record_name in deps.module_record_names:
|
|
845
|
+
record = deps.records_by_name[record_name]
|
|
846
|
+
if record.type_params:
|
|
847
|
+
tparams = ", ".join(f"typename {tp}" for tp in record.type_params)
|
|
848
|
+
hpp.write(f"template<{tparams}> struct {record_name};\n")
|
|
849
|
+
else:
|
|
850
|
+
hpp.write(f"struct {record_name};\n")
|
|
851
|
+
|
|
852
|
+
if deps.prereq_protocol_records & deps.module_record_names:
|
|
853
|
+
hpp.write("\n")
|
|
854
|
+
|
|
855
|
+
# Prereq protocol concepts
|
|
856
|
+
for protocol in module.protocols:
|
|
857
|
+
if protocol.name in deps.prereq_protocols:
|
|
858
|
+
self._emit_concept_and_dynamic(hpp, protocol)
|
|
859
|
+
|
|
860
|
+
# Forward declare records referenced by bound protocols
|
|
861
|
+
for record_name in sorted(deps.bound_protocol_records):
|
|
862
|
+
if record_name in deps.module_record_names:
|
|
863
|
+
if record_name in deps.prereq_protocol_records:
|
|
864
|
+
continue
|
|
865
|
+
record = deps.records_by_name[record_name]
|
|
866
|
+
if record.type_params:
|
|
867
|
+
if record.type_param_bounds or record.type_param_kinds:
|
|
868
|
+
template_header = self.protocols.gen_record_template_header(
|
|
869
|
+
record.type_params, record.type_param_bounds, record.type_param_kinds
|
|
870
|
+
)
|
|
871
|
+
hpp.write(f"{template_header} struct {record_name};\n")
|
|
872
|
+
else:
|
|
873
|
+
tparams = ", ".join(f"typename {tp}" for tp in record.type_params)
|
|
874
|
+
hpp.write(f"template<{tparams}> struct {record_name};\n")
|
|
875
|
+
else:
|
|
876
|
+
hpp.write(f"struct {record_name};\n")
|
|
877
|
+
|
|
878
|
+
if deps.bound_protocol_records & deps.module_record_names:
|
|
879
|
+
hpp.write("\n")
|
|
880
|
+
|
|
881
|
+
# Bound protocol concepts (skip prereq protocols already emitted)
|
|
882
|
+
for protocol in module.protocols:
|
|
883
|
+
if protocol.name in deps.bound_protocols and protocol.name not in deps.prereq_protocols:
|
|
884
|
+
self._emit_concept_and_dynamic(hpp, protocol)
|
|
885
|
+
|
|
886
|
+
# Fully define records referenced by bound protocols
|
|
887
|
+
for record in module.records:
|
|
888
|
+
if record.name in deps.bound_protocol_records:
|
|
889
|
+
self.records.gen_record_decl(hpp, record)
|
|
890
|
+
self._emit_hash_specialization(hpp, record)
|
|
891
|
+
self._emit_nested_hash_specializations(hpp, record)
|
|
892
|
+
self._emit_value_type_spec(hpp, record)
|
|
893
|
+
self._emit_nested_value_type_specs(hpp, record)
|
|
894
|
+
hpp.write("\n")
|
|
895
|
+
|
|
896
|
+
# Full definitions for records that are type args to bounded records
|
|
897
|
+
for record in module.records:
|
|
898
|
+
if record.name in deps.early_definition_records:
|
|
899
|
+
if record.name in deps.bound_protocol_records:
|
|
900
|
+
continue
|
|
901
|
+
self.records.gen_record_decl(hpp, record)
|
|
902
|
+
self._emit_hash_specialization(hpp, record)
|
|
903
|
+
self._emit_nested_hash_specializations(hpp, record)
|
|
904
|
+
self._emit_value_type_spec(hpp, record)
|
|
905
|
+
self._emit_nested_value_type_specs(hpp, record)
|
|
906
|
+
hpp.write("\n")
|
|
907
|
+
|
|
908
|
+
# Forward declare records referenced in protocols (bounds now available)
|
|
909
|
+
for record_name in sorted(deps.protocol_referenced_records):
|
|
910
|
+
if record_name in deps.module_record_names:
|
|
911
|
+
if record_name in deps.early_definition_records:
|
|
912
|
+
continue
|
|
913
|
+
if record_name in deps.bound_protocol_records:
|
|
914
|
+
continue
|
|
915
|
+
if record_name in deps.prereq_protocol_records:
|
|
916
|
+
continue
|
|
917
|
+
record = deps.records_by_name[record_name]
|
|
918
|
+
if record.type_params:
|
|
919
|
+
template_header = self.protocols.gen_record_template_header(
|
|
920
|
+
record.type_params, record.type_param_bounds, record.type_param_kinds
|
|
921
|
+
)
|
|
922
|
+
hpp.write(f"{template_header} struct {record_name};\n")
|
|
923
|
+
else:
|
|
924
|
+
hpp.write(f"struct {record_name};\n")
|
|
925
|
+
|
|
926
|
+
if deps.protocol_referenced_records & deps.module_record_names:
|
|
927
|
+
hpp.write("\n")
|
|
928
|
+
|
|
929
|
+
# Remaining C++20 concepts for user-defined protocols
|
|
930
|
+
for protocol in module.protocols:
|
|
931
|
+
if protocol.name not in deps.bound_protocols and protocol.name not in deps.prereq_protocols:
|
|
932
|
+
self._emit_concept_and_dynamic(hpp, protocol)
|
|
933
|
+
|
|
934
|
+
def _generate_definitions_and_reexports(
|
|
935
|
+
self, hpp: TextIO, module: TpyModule,
|
|
936
|
+
global_decls: list, final_decls: list, seen_globals: dict, deps: _ProtocolDeps
|
|
937
|
+
) -> None:
|
|
938
|
+
"""Generate remaining forward decls, global externs, record definitions, functions, and re-exports."""
|
|
939
|
+
# Hash + is_value_type specs for @native records. The main per-record
|
|
940
|
+
# emit path (`sort_records_by_inheritance`) filters native records out,
|
|
941
|
+
# so without this pass `val_or_ref_t<NativeT>` lands on the false-type
|
|
942
|
+
# default (T&) in generic instantiations -- wrong ABI for a value type --
|
|
943
|
+
# and any TPy-declared `__hash__` on a native record loses its
|
|
944
|
+
# `std::hash` spec, breaking dict/set use. Emitted before any record
|
|
945
|
+
# body / function body that might instantiate on the native type.
|
|
946
|
+
for record in module.all_records():
|
|
947
|
+
if self._is_native_record(record.name):
|
|
948
|
+
self._emit_hash_specialization(hpp, record)
|
|
949
|
+
self._emit_value_type_spec(hpp, record)
|
|
950
|
+
|
|
951
|
+
# Enum class declarations (before records, since records may have enum fields).
|
|
952
|
+
# @native enums skip the declaration entirely -- the user's
|
|
953
|
+
# `# tpy: include(...)` directive provides the C++ enum class.
|
|
954
|
+
for enum in module.enums:
|
|
955
|
+
if enum.is_native:
|
|
956
|
+
continue
|
|
957
|
+
self._gen_enum_decl(hpp, enum)
|
|
958
|
+
|
|
959
|
+
# Top-level EnumUtil specs must be at global scope. Nested enums
|
|
960
|
+
# need their parent struct defined first (emitted after record
|
|
961
|
+
# definitions). @native enums still get EnumUtil (reflection
|
|
962
|
+
# across the binding boundary) but no operator<< -- that's
|
|
963
|
+
# emitted by the user if needed, and TPy's print/repr paths
|
|
964
|
+
# route through __repr__ via the runtime template gated on
|
|
965
|
+
# EnumUtil presence.
|
|
966
|
+
#
|
|
967
|
+
# The `tpy::is_dyn_protocol_base<P>` trait specializations for
|
|
968
|
+
# @dynamic protocols are emitted here too. They have to come
|
|
969
|
+
# before any in-module code that triggers an implicit
|
|
970
|
+
# instantiation of the primary template (which happens whenever
|
|
971
|
+
# `Box<P>`, `Adapter<P, T>`, etc. is referenced -- inline record
|
|
972
|
+
# method bodies emitted later in the same header are the
|
|
973
|
+
# typical culprits). Specializing after that point is a hard
|
|
974
|
+
# C++ error ("specialization after instantiation").
|
|
975
|
+
#
|
|
976
|
+
# The full Adapter / RefAdapter specs are deferred until after
|
|
977
|
+
# records (see below): their override bodies pass protocol-
|
|
978
|
+
# method parameter types (e.g. `Waker`) by value, so those
|
|
979
|
+
# types must be complete at the spec definition site.
|
|
980
|
+
dynamic_protocols = [p for p in module.protocols if p.is_dynamic]
|
|
981
|
+
top_enums = module.enums
|
|
982
|
+
if top_enums or dynamic_protocols:
|
|
983
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
984
|
+
hpp.write(f"}} // namespace {ns}\n\n")
|
|
985
|
+
for protocol in dynamic_protocols:
|
|
986
|
+
self.protocols.gen_dyn_protocol_base_trait(hpp, protocol, ns)
|
|
987
|
+
if top_enums:
|
|
988
|
+
self._gen_enum_util_decls_for(hpp, top_enums)
|
|
989
|
+
hpp.write(f"namespace {ns} {{\n\n")
|
|
990
|
+
for enum in top_enums:
|
|
991
|
+
if enum.is_native:
|
|
992
|
+
continue
|
|
993
|
+
self._gen_enum_operator_ostream(hpp, enum)
|
|
994
|
+
|
|
995
|
+
# Forward declare recursive union wrapper structs (before records,
|
|
996
|
+
# so that record fields like Box[JsonValue] can reference the name)
|
|
997
|
+
emitted_fwd = False
|
|
998
|
+
for name, entry in module.type_aliases.items():
|
|
999
|
+
if name in module.recursive_union_names:
|
|
1000
|
+
type_params = entry[2]
|
|
1001
|
+
# Wrappers (generic and non-generic) are forward-declared
|
|
1002
|
+
# before concepts when the module has protocols (see
|
|
1003
|
+
# _generate_forward_decls_and_concepts); avoid a duplicate.
|
|
1004
|
+
if module.protocols:
|
|
1005
|
+
continue
|
|
1006
|
+
if type_params:
|
|
1007
|
+
header = self.protocols.gen_record_template_header(
|
|
1008
|
+
type_params, {}, entry[3])
|
|
1009
|
+
hpp.write(f"{header} struct {name};\n")
|
|
1010
|
+
else:
|
|
1011
|
+
hpp.write(f"struct {name};\n")
|
|
1012
|
+
emitted_fwd = True
|
|
1013
|
+
|
|
1014
|
+
# Forward declare remaining records (excluding native records)
|
|
1015
|
+
for record in module.records:
|
|
1016
|
+
if self._is_native_record(record.name):
|
|
1017
|
+
continue
|
|
1018
|
+
if record.name in deps.protocol_referenced_records:
|
|
1019
|
+
continue
|
|
1020
|
+
if record.name in deps.early_definition_records:
|
|
1021
|
+
continue
|
|
1022
|
+
if record.name in deps.bound_protocol_records:
|
|
1023
|
+
continue
|
|
1024
|
+
if record.name in deps.prereq_protocol_records:
|
|
1025
|
+
continue
|
|
1026
|
+
if record.type_params:
|
|
1027
|
+
template_header = self.protocols.gen_record_template_header(
|
|
1028
|
+
record.type_params, record.type_param_bounds, record.type_param_kinds
|
|
1029
|
+
)
|
|
1030
|
+
hpp.write(f"{template_header} struct {record.name};\n")
|
|
1031
|
+
else:
|
|
1032
|
+
hpp.write(f"struct {record.name};\n")
|
|
1033
|
+
emitted_fwd = True
|
|
1034
|
+
# Ensure spacing between forward decl section and externs when records exist
|
|
1035
|
+
has_non_native_records = any(not self._is_native_record(r.name) for r in module.records)
|
|
1036
|
+
if emitted_fwd or has_non_native_records:
|
|
1037
|
+
hpp.write("\n")
|
|
1038
|
+
|
|
1039
|
+
# Global extern declarations
|
|
1040
|
+
for stmt in global_decls:
|
|
1041
|
+
self.functions.gen_global_extern(hpp, stmt)
|
|
1042
|
+
# Final global declarations (inline constexpr or extern const)
|
|
1043
|
+
for stmt in final_decls:
|
|
1044
|
+
self.functions.gen_final_global_header(hpp, stmt)
|
|
1045
|
+
hpp.write("\n")
|
|
1046
|
+
|
|
1047
|
+
# Imported type alias using-declarations (before function forward
|
|
1048
|
+
# decls so signatures can reference alias names like Shape)
|
|
1049
|
+
emitted_imported_alias = False
|
|
1050
|
+
for local_name, (src_mod, original_name) in sorted(
|
|
1051
|
+
self.analyzer.registry.imported_type_alias_info.items()):
|
|
1052
|
+
# Skip aliases whose C++ emission bypasses the Python name --
|
|
1053
|
+
# the `using PythonName = ...;` would be dead code. See
|
|
1054
|
+
# _emits_own_cpp above.
|
|
1055
|
+
alias_info = self.analyzer.registry.get_type_alias_info(local_name)
|
|
1056
|
+
if alias_info is not None:
|
|
1057
|
+
if alias_info.type_params:
|
|
1058
|
+
# Generic non-recursive aliases have no C++-level
|
|
1059
|
+
# identity (the source module skipped emission), so
|
|
1060
|
+
# emitting `using Local = Src::Original;` would
|
|
1061
|
+
# dangle. See
|
|
1062
|
+
# docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md.
|
|
1063
|
+
continue
|
|
1064
|
+
alias_type = alias_info.body
|
|
1065
|
+
if isinstance(alias_type, NominalType) and _emits_own_cpp(alias_type):
|
|
1066
|
+
continue
|
|
1067
|
+
if not isinstance(alias_type, (NominalType, UnionType)):
|
|
1068
|
+
continue
|
|
1069
|
+
qualified = qualified_cpp_name(src_mod, original_name)
|
|
1070
|
+
if local_name == original_name:
|
|
1071
|
+
hpp.write(f"using {qualified};\n")
|
|
1072
|
+
else:
|
|
1073
|
+
hpp.write(f"using {local_name} = {qualified};\n")
|
|
1074
|
+
emitted_imported_alias = True
|
|
1075
|
+
if emitted_imported_alias:
|
|
1076
|
+
hpp.write("\n")
|
|
1077
|
+
|
|
1078
|
+
# Generator struct forward declarations (so factory forward decls
|
|
1079
|
+
# can reference the struct type name).
|
|
1080
|
+
emitted_gen_fwd = False
|
|
1081
|
+
for func in module.functions:
|
|
1082
|
+
if func.skip_codegen:
|
|
1083
|
+
continue
|
|
1084
|
+
if func.is_generator and not self.gen_generators.is_simple_generator(func):
|
|
1085
|
+
if self._resumable_generator_eligible(func):
|
|
1086
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
1087
|
+
self.gen_async.gen_coro_forward_decl(hpp, func)
|
|
1088
|
+
emitted_gen_fwd = True
|
|
1089
|
+
if func.is_async:
|
|
1090
|
+
self.gen_async.gen_coro_forward_decl(hpp, func)
|
|
1091
|
+
emitted_gen_fwd = True
|
|
1092
|
+
# Method generator/async struct forward declarations (before records)
|
|
1093
|
+
for record in module.all_records():
|
|
1094
|
+
for method in record.methods:
|
|
1095
|
+
if method.is_generator and not self.gen_generators.is_simple_generator(method):
|
|
1096
|
+
if self._resumable_generator_eligible(method):
|
|
1097
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
1098
|
+
self.gen_async.gen_coro_forward_decl(
|
|
1099
|
+
hpp, method, record_name=record.name)
|
|
1100
|
+
emitted_gen_fwd = True
|
|
1101
|
+
if method.is_async:
|
|
1102
|
+
self.gen_async.gen_coro_forward_decl(
|
|
1103
|
+
hpp, method, record_name=record.name)
|
|
1104
|
+
emitted_gen_fwd = True
|
|
1105
|
+
if emitted_gen_fwd:
|
|
1106
|
+
hpp.write("\n")
|
|
1107
|
+
|
|
1108
|
+
# Function forward declarations (before records, so inline
|
|
1109
|
+
# constructor/method bodies can call free functions).
|
|
1110
|
+
# Functions whose signatures reference nested types are deferred
|
|
1111
|
+
# until after record definitions (the parent struct must be complete).
|
|
1112
|
+
deferred_fwd_funcs: list[TpyFunction] = []
|
|
1113
|
+
emitted_fwd_func = False
|
|
1114
|
+
for func in module.functions:
|
|
1115
|
+
if func.skip_codegen:
|
|
1116
|
+
continue
|
|
1117
|
+
if func.is_generator:
|
|
1118
|
+
if self.gen_generators.is_simple_generator(func):
|
|
1119
|
+
continue # Simple generators are inline -- no forward decl
|
|
1120
|
+
if self._resumable_generator_eligible(func):
|
|
1121
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
1122
|
+
if self.gen_async.gen_factory_forward_decl(hpp, func):
|
|
1123
|
+
emitted_fwd_func = True
|
|
1124
|
+
elif func.is_async:
|
|
1125
|
+
if self.gen_async.gen_factory_forward_decl(hpp, func):
|
|
1126
|
+
emitted_fwd_func = True
|
|
1127
|
+
elif _func_uses_nested_type(func):
|
|
1128
|
+
deferred_fwd_funcs.append(func)
|
|
1129
|
+
elif self.functions.gen_function_forward_decl(hpp, func):
|
|
1130
|
+
emitted_fwd_func = True
|
|
1131
|
+
if emitted_fwd_func:
|
|
1132
|
+
hpp.write("\n")
|
|
1133
|
+
|
|
1134
|
+
# Generic recursive-alias wrapper structs are templates: member
|
|
1135
|
+
# completeness is required only at instantiation, not at the template
|
|
1136
|
+
# definition, so they precede records that embed them by value (a
|
|
1137
|
+
# `Tree[Int32]` field needs `Tree` complete). Non-generic wrappers stay
|
|
1138
|
+
# after records -- their variant stores the member records by value, so
|
|
1139
|
+
# those must be complete first.
|
|
1140
|
+
emitted_generic_wrapper = False
|
|
1141
|
+
for name, entry in sorted(module.type_aliases.items()):
|
|
1142
|
+
typ, _loc, type_params, _kinds = entry
|
|
1143
|
+
if name in module.recursive_union_names and type_params:
|
|
1144
|
+
self._gen_recursive_union_struct(hpp, name, typ, type_params, _kinds)
|
|
1145
|
+
emitted_generic_wrapper = True
|
|
1146
|
+
if emitted_generic_wrapper:
|
|
1147
|
+
hpp.write("\n")
|
|
1148
|
+
|
|
1149
|
+
# Full record definitions (skip those already defined early)
|
|
1150
|
+
sorted_records = self.records.sort_records_by_inheritance(module.records)
|
|
1151
|
+
for record in sorted_records:
|
|
1152
|
+
if record.name in deps.early_definition_records:
|
|
1153
|
+
continue
|
|
1154
|
+
if record.name in deps.bound_protocol_records:
|
|
1155
|
+
continue
|
|
1156
|
+
self.records.gen_record_decl(hpp, record)
|
|
1157
|
+
self._emit_hash_specialization(hpp, record)
|
|
1158
|
+
self._emit_nested_hash_specializations(hpp, record)
|
|
1159
|
+
# is_value_type spec MUST precede any subsequent inline method
|
|
1160
|
+
# body / generator struct / coro struct that template-instantiates
|
|
1161
|
+
# on this record. Per-record placement (same pattern as the hash
|
|
1162
|
+
# spec above) makes the ordering structural rather than a
|
|
1163
|
+
# codegen-pass invariant.
|
|
1164
|
+
self._emit_value_type_spec(hpp, record)
|
|
1165
|
+
self._emit_nested_value_type_specs(hpp, record)
|
|
1166
|
+
hpp.write("\n")
|
|
1167
|
+
|
|
1168
|
+
# Deferred forward declarations for functions with nested types
|
|
1169
|
+
for func in deferred_fwd_funcs:
|
|
1170
|
+
self.functions.gen_function_forward_decl(hpp, func)
|
|
1171
|
+
if deferred_fwd_funcs:
|
|
1172
|
+
hpp.write("\n")
|
|
1173
|
+
|
|
1174
|
+
# Dynamic protocol Adapter / RefAdapter specs at global scope.
|
|
1175
|
+
# Emitted here (rather than right after the protocol forward
|
|
1176
|
+
# decl) so that all value-type records referenced by protocol
|
|
1177
|
+
# method signatures are complete -- the Adapter override body
|
|
1178
|
+
# passes those types by value, which a function definition
|
|
1179
|
+
# requires complete. Subsequent in-module use sites (templated
|
|
1180
|
+
# method bodies emitted further down) see the spec.
|
|
1181
|
+
if dynamic_protocols:
|
|
1182
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
1183
|
+
hpp.write(f"}} // namespace {ns}\n\n")
|
|
1184
|
+
self._gen_dynamic_adapter_specs(hpp, module, dynamic_protocols)
|
|
1185
|
+
hpp.write(f"namespace {ns} {{\n\n")
|
|
1186
|
+
|
|
1187
|
+
# EnumUtil + operator<< for nested enums (must come after parent struct definitions)
|
|
1188
|
+
nested_enums = [e for e in module.all_enums() if "." in e.name]
|
|
1189
|
+
if nested_enums:
|
|
1190
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
1191
|
+
hpp.write(f"}} // namespace {ns}\n\n")
|
|
1192
|
+
self._gen_enum_util_decls_for(hpp, nested_enums)
|
|
1193
|
+
hpp.write(f"namespace {ns} {{\n\n")
|
|
1194
|
+
for enum in nested_enums:
|
|
1195
|
+
# Parser currently rejects nested @native enums, but guard
|
|
1196
|
+
# defensively so a future relaxation can't silently emit
|
|
1197
|
+
# operator<< for a native enum (would conflict with any
|
|
1198
|
+
# user-provided one in their C++ namespace).
|
|
1199
|
+
if enum.is_native:
|
|
1200
|
+
continue
|
|
1201
|
+
self._gen_enum_operator_ostream(hpp, enum)
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
# Async-method coro struct definitions FIRST (before any free coro
|
|
1205
|
+
# struct that might inline them as `optional<__coro_Class_method>`
|
|
1206
|
+
# sub-future fields -- needs the full type, not just the forward
|
|
1207
|
+
# decl emitted before records). Method factory definitions land
|
|
1208
|
+
# immediately after their struct so the inline factory body sees
|
|
1209
|
+
# the complete coro type.
|
|
1210
|
+
for record in module.all_records():
|
|
1211
|
+
for method in record.methods:
|
|
1212
|
+
if not method.is_async:
|
|
1213
|
+
continue
|
|
1214
|
+
self.gen_async.gen_coro_struct(
|
|
1215
|
+
hpp, method, record_name=record.name)
|
|
1216
|
+
hpp.write("\n")
|
|
1217
|
+
# Templates (type params OR a protocol-typed param OR a
|
|
1218
|
+
# method on a generic class): poll() body must be
|
|
1219
|
+
# inline-in-header, else the out-of-line .cpp body fails to
|
|
1220
|
+
# link against the template struct.
|
|
1221
|
+
if self.gen_async._is_templated_coro(method, record.name):
|
|
1222
|
+
self.gen_async.gen_coro_poll_def(
|
|
1223
|
+
hpp, method, record_name=record.name)
|
|
1224
|
+
hpp.write("\n")
|
|
1225
|
+
self.gen_async.gen_coro_finally_top_def(
|
|
1226
|
+
hpp, method, record_name=record.name)
|
|
1227
|
+
hpp.write("\n")
|
|
1228
|
+
# Templated factory body too: zero-arg generic async defs
|
|
1229
|
+
# would otherwise fall off CTAD.
|
|
1230
|
+
struct_name = self.gen_async._struct_name_templated(method, record.name)
|
|
1231
|
+
cpp_record = escape_cpp_name(record.name.replace(".", "::"))
|
|
1232
|
+
# Out-of-class definition of a member of a class template
|
|
1233
|
+
# qualifies the class name with its template args
|
|
1234
|
+
# (`Box<T>::get()`, not `Box::get()`).
|
|
1235
|
+
record_tps = self.gen_async._record_template_args(record.name)
|
|
1236
|
+
if record_tps:
|
|
1237
|
+
cpp_record = f"{cpp_record}<{', '.join(record_tps)}>"
|
|
1238
|
+
params = self.functions.gen_params(
|
|
1239
|
+
method.params, method.type_params,
|
|
1240
|
+
emit_defaults=False, func=method)
|
|
1241
|
+
args = self.gen_async._factory_args_forwarded(
|
|
1242
|
+
method, receiver=(record.name, "*this"))
|
|
1243
|
+
const_suffix = " const" if method.is_readonly else ""
|
|
1244
|
+
self.gen_async._emit_member_template_headers(hpp, method, record_name=record.name)
|
|
1245
|
+
hpp.write(f"inline {struct_name} {cpp_record}::{method.name}({params}){const_suffix} {{\n")
|
|
1246
|
+
hpp.write(f" return {struct_name}({args});\n")
|
|
1247
|
+
hpp.write(f"}}\n\n")
|
|
1248
|
+
|
|
1249
|
+
# Generator struct full definitions (after records, so struct fields
|
|
1250
|
+
# and inline __next__() can use fully-defined user types).
|
|
1251
|
+
for func in module.functions:
|
|
1252
|
+
if func.skip_codegen:
|
|
1253
|
+
continue
|
|
1254
|
+
if func.is_generator and not self.gen_generators.is_simple_generator(func):
|
|
1255
|
+
if self._resumable_generator_eligible(func):
|
|
1256
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
1257
|
+
self.gen_async.gen_coro_struct(hpp, func)
|
|
1258
|
+
# Templated generators: __next__ body + factory must
|
|
1259
|
+
# be inline-in-header (template definitions can't go
|
|
1260
|
+
# in the .cpp). Emit right after the struct so they
|
|
1261
|
+
# see fully-defined fields. Mirrors the generic
|
|
1262
|
+
# async-def branch below.
|
|
1263
|
+
if self.gen_async._is_templated_coro(func):
|
|
1264
|
+
self.gen_async.gen_coro_poll_def(hpp, func)
|
|
1265
|
+
hpp.write("\n")
|
|
1266
|
+
self.gen_async.gen_coro_finally_top_def(hpp, func)
|
|
1267
|
+
hpp.write("\n")
|
|
1268
|
+
self.gen_async.gen_factory(hpp, func)
|
|
1269
|
+
hpp.write("\n")
|
|
1270
|
+
if func.is_async:
|
|
1271
|
+
self.gen_async.gen_coro_struct(hpp, func)
|
|
1272
|
+
# Templates (type params OR a protocol-typed param): poll()
|
|
1273
|
+
# body must be inline-in-header. Emit it right after the
|
|
1274
|
+
# struct so it sees fully-defined fields.
|
|
1275
|
+
if self.gen_async._is_templated_coro(func):
|
|
1276
|
+
self.gen_async.gen_coro_poll_def(hpp, func)
|
|
1277
|
+
hpp.write("\n")
|
|
1278
|
+
self.gen_async.gen_coro_finally_top_def(hpp, func)
|
|
1279
|
+
hpp.write("\n")
|
|
1280
|
+
self.gen_async.gen_factory(hpp, func)
|
|
1281
|
+
hpp.write("\n")
|
|
1282
|
+
# Method generator struct definitions + out-of-line factory methods
|
|
1283
|
+
for record in module.all_records():
|
|
1284
|
+
for method in record.methods:
|
|
1285
|
+
if method.is_generator and not self.gen_generators.is_simple_generator(method):
|
|
1286
|
+
if self._resumable_generator_eligible(method):
|
|
1287
|
+
with self.gen_async._resumable_shape(ResumableShape.GENERATOR):
|
|
1288
|
+
self.gen_async.gen_coro_struct(
|
|
1289
|
+
hpp, method, record_name=record.name)
|
|
1290
|
+
hpp.write("\n")
|
|
1291
|
+
# Templated generator methods (own type params, a
|
|
1292
|
+
# proto-typed param, OR a method on a generic
|
|
1293
|
+
# class): __next__ body + finally-top must be
|
|
1294
|
+
# inline-in-header.
|
|
1295
|
+
if self.gen_async._is_templated_coro(method, record.name):
|
|
1296
|
+
self.gen_async.gen_coro_poll_def(
|
|
1297
|
+
hpp, method, record_name=record.name)
|
|
1298
|
+
hpp.write("\n")
|
|
1299
|
+
self.gen_async.gen_coro_finally_top_def(
|
|
1300
|
+
hpp, method, record_name=record.name)
|
|
1301
|
+
hpp.write("\n")
|
|
1302
|
+
# Inline factory method (mirrors the async-method
|
|
1303
|
+
# factory: _factory_args_forwarded moves Own[T] /
|
|
1304
|
+
# protocol params in correctly).
|
|
1305
|
+
struct_name = self.gen_async._struct_name_templated(method, record.name)
|
|
1306
|
+
cpp_record = escape_cpp_name(record.name.replace(".", "::"))
|
|
1307
|
+
# Qualify with class template args for
|
|
1308
|
+
# out-of-class member definitions of a class
|
|
1309
|
+
# template (e.g. `Box<T>::items`).
|
|
1310
|
+
record_tps = self.gen_async._record_template_args(record.name)
|
|
1311
|
+
if record_tps:
|
|
1312
|
+
cpp_record = f"{cpp_record}<{', '.join(record_tps)}>"
|
|
1313
|
+
params = self.functions.gen_params(
|
|
1314
|
+
method.params, method.type_params,
|
|
1315
|
+
emit_defaults=False, func=method)
|
|
1316
|
+
args = self.gen_async._factory_args_forwarded(
|
|
1317
|
+
method, receiver=(record.name, "*this"))
|
|
1318
|
+
const_suffix = " const" if method.is_readonly else ""
|
|
1319
|
+
self.gen_async._emit_member_template_headers(hpp, method, record_name=record.name)
|
|
1320
|
+
hpp.write(f"inline {struct_name} {cpp_record}::{method.name}({params}){const_suffix} {{\n")
|
|
1321
|
+
hpp.write(f" return {struct_name}({args});\n")
|
|
1322
|
+
hpp.write(f"}}\n\n")
|
|
1323
|
+
|
|
1324
|
+
# Trivial out-of-line method bodies (single-statement getters /
|
|
1325
|
+
# setters / forwarders) stay in the .hpp with ``inline`` so the
|
|
1326
|
+
# compiler can inline at the call site without LTO. Larger bodies
|
|
1327
|
+
# land in the .cpp via the matching pass at the end of `generate()`.
|
|
1328
|
+
# Cycle members skip the inline-in-header pass: any method body
|
|
1329
|
+
# that touches a cycle peer's type would need that peer's
|
|
1330
|
+
# complete header included from .hpp, which `<peer>_fwd.hpp`
|
|
1331
|
+
# doesn't carry.
|
|
1332
|
+
if not self.ctx.cycle_peers:
|
|
1333
|
+
for record in sorted_records:
|
|
1334
|
+
self.records.gen_record_method_defs(hpp, record, mode="def_hpp")
|
|
1335
|
+
|
|
1336
|
+
# std::hash specializations are emitted per-record inline (see
|
|
1337
|
+
# _emit_hash_specialization), not batched here. This ensures hash
|
|
1338
|
+
# specs appear before any struct that uses the type as a set/dict-key.
|
|
1339
|
+
|
|
1340
|
+
# Module-local type alias definitions (after record definitions
|
|
1341
|
+
# so member types are complete for std::variant).
|
|
1342
|
+
#
|
|
1343
|
+
# Non-recursive generic aliases (`type Pair[T] = ...`) are
|
|
1344
|
+
# expanded at use sites by the parser-resolver (Phase 1 of
|
|
1345
|
+
# generic recursive aliases; see
|
|
1346
|
+
# docs/GENERIC_RECURSIVE_ALIASES_DESIGN.md), so they have no
|
|
1347
|
+
# C++-level identity and we skip emission entirely. The body
|
|
1348
|
+
# still contains TypeParamRef placeholders, which would render
|
|
1349
|
+
# as bare `T` -- invalid C++ at module scope.
|
|
1350
|
+
emitted_alias = False
|
|
1351
|
+
for name, entry in sorted(module.type_aliases.items()):
|
|
1352
|
+
typ, _loc, type_params, _kinds = entry
|
|
1353
|
+
if name in module.recursive_union_names:
|
|
1354
|
+
if type_params:
|
|
1355
|
+
continue # generic recursive wrapper emitted before records
|
|
1356
|
+
self._gen_recursive_union_struct(
|
|
1357
|
+
hpp, name, typ, type_params, _kinds)
|
|
1358
|
+
elif type_params:
|
|
1359
|
+
continue # generic non-recursive alias: no C++ emission
|
|
1360
|
+
else:
|
|
1361
|
+
cpp_type = self.types.type_to_cpp(typ)
|
|
1362
|
+
hpp.write(f"using {name} = {cpp_type};\n")
|
|
1363
|
+
emitted_alias = True
|
|
1364
|
+
if emitted_alias:
|
|
1365
|
+
hpp.write("\n")
|
|
1366
|
+
# Register module-local union aliases AFTER emitting the using
|
|
1367
|
+
# declaration (to avoid circular `using Shape = Shape;`) but
|
|
1368
|
+
# BEFORE function definitions (so signatures use the alias name).
|
|
1369
|
+
# Skip generic aliases: they have no name-level C++ identity, and
|
|
1370
|
+
# registering their substituted body under the alias name would
|
|
1371
|
+
# collide with downstream code that expects union alias names to
|
|
1372
|
+
# be C++-visible.
|
|
1373
|
+
for name, entry in module.type_aliases.items():
|
|
1374
|
+
typ, _loc, type_params, _kinds = entry
|
|
1375
|
+
if not type_params and isinstance(typ, UnionType):
|
|
1376
|
+
register_union_alias(typ.members, name)
|
|
1377
|
+
|
|
1378
|
+
# Function declarations (template definitions, stubs, and extern "C";
|
|
1379
|
+
# non-template signatures are already forward-declared above)
|
|
1380
|
+
emitted_func_decl = False
|
|
1381
|
+
for func in module.functions:
|
|
1382
|
+
if func.skip_codegen:
|
|
1383
|
+
continue
|
|
1384
|
+
if func.is_generator:
|
|
1385
|
+
if self.gen_generators.is_simple_generator(func):
|
|
1386
|
+
# Simple generators: emit inline function definition in header
|
|
1387
|
+
self.gen_generators.gen_simple_generator_inline(hpp, func)
|
|
1388
|
+
hpp.write("\n")
|
|
1389
|
+
emitted_func_decl = True
|
|
1390
|
+
continue # Complex generators: factory emitted in source file
|
|
1391
|
+
if func.is_async:
|
|
1392
|
+
# Async defs go through AsyncCoroCodegen.gen_factory_forward_decl
|
|
1393
|
+
# earlier in the pipeline; gen_function_decl would otherwise
|
|
1394
|
+
# emit a stray sync template stub for generic async defs.
|
|
1395
|
+
continue
|
|
1396
|
+
if self.functions.gen_function_decl(hpp, func):
|
|
1397
|
+
emitted_func_decl = True
|
|
1398
|
+
|
|
1399
|
+
if emitted_func_decl:
|
|
1400
|
+
hpp.write("\n")
|
|
1401
|
+
|
|
1402
|
+
def _gen_reexport_using_decls(self, hpp: TextIO) -> None:
|
|
1403
|
+
"""Emit `using` declarations for re-exported symbols (functions /
|
|
1404
|
+
records / enums / variables). One pass over the per-module
|
|
1405
|
+
attribute table buckets cells by kind; each block below emits
|
|
1406
|
+
its `using` / `inline auto&` shape from the bucketed entry.
|
|
1407
|
+
"""
|
|
1408
|
+
table = self.analyzer.ctx.module_attributes or {}
|
|
1409
|
+
func_reexports: list[tuple[str, str, str, object]] = []
|
|
1410
|
+
record_reexports: list[tuple[str, str, str, object]] = []
|
|
1411
|
+
protocol_reexports: list[tuple[str, str, str, object]] = []
|
|
1412
|
+
enum_reexports: list[tuple[str, str, str, object]] = []
|
|
1413
|
+
var_reexports: list[tuple[str, str, str, object]] = []
|
|
1414
|
+
for name, cell in table.items():
|
|
1415
|
+
bd = cell.binding
|
|
1416
|
+
if bd.defining_module is None:
|
|
1417
|
+
continue # local definition
|
|
1418
|
+
# The binding's `info` carries the kind-specific payload
|
|
1419
|
+
# (function infos / RecordInfo / enum NominalType / variable
|
|
1420
|
+
# TpyType). Pass it through so the kind-specific blocks
|
|
1421
|
+
# below can apply their filters (decorator stubs, native,
|
|
1422
|
+
# cpp_template, special_handling) even for direct imports
|
|
1423
|
+
# from implicit-stdlib roots where the consumer module's
|
|
1424
|
+
# registry isn't populated.
|
|
1425
|
+
entry = (name, bd.defining_module, bd.canonical_name, bd.info)
|
|
1426
|
+
if bd.kind == SymbolKind.FUNCTION:
|
|
1427
|
+
func_reexports.append(entry)
|
|
1428
|
+
elif bd.kind == SymbolKind.RECORD:
|
|
1429
|
+
record_reexports.append(entry)
|
|
1430
|
+
elif bd.kind == SymbolKind.PROTOCOL_DYNAMIC:
|
|
1431
|
+
# @dynamic protocols compile to abstract base classes
|
|
1432
|
+
# (real types), so a `using X = ns::Base;` alias works.
|
|
1433
|
+
# Static protocols compile to C++ concepts, which need
|
|
1434
|
+
# template-form alias syntax incompatible with the
|
|
1435
|
+
# bucketing emit path; consumers reach them via full
|
|
1436
|
+
# qualification instead.
|
|
1437
|
+
protocol_reexports.append(entry)
|
|
1438
|
+
elif bd.kind == SymbolKind.ENUM:
|
|
1439
|
+
enum_reexports.append(entry)
|
|
1440
|
+
elif bd.kind == SymbolKind.VARIABLE:
|
|
1441
|
+
var_reexports.append(entry)
|
|
1442
|
+
func_reexports.sort()
|
|
1443
|
+
record_reexports.sort()
|
|
1444
|
+
protocol_reexports.sort()
|
|
1445
|
+
enum_reexports.sort()
|
|
1446
|
+
var_reexports.sort()
|
|
1447
|
+
|
|
1448
|
+
# Re-exported functions
|
|
1449
|
+
if func_reexports:
|
|
1450
|
+
any_written = False
|
|
1451
|
+
for local_name, source_module, original_name, info in func_reexports:
|
|
1452
|
+
# Cycle suppression: functions aren't forward-declared
|
|
1453
|
+
# in `<peer>_fwd.hpp`, so a `using` for a cycle peer's
|
|
1454
|
+
# function would reach into the peer's full header.
|
|
1455
|
+
# Consumers qualify through the defining module
|
|
1456
|
+
# directly, so this is interop-surface only.
|
|
1457
|
+
if source_module in self.ctx.cycle_peers:
|
|
1458
|
+
continue
|
|
1459
|
+
# `info` is the FunctionInfo list from the binding (set
|
|
1460
|
+
# at install time); prefer it over registry.get_function
|
|
1461
|
+
# which is None for direct implicit-stdlib imports
|
|
1462
|
+
# bypassing the consumer's local registry.
|
|
1463
|
+
func_infos = info if isinstance(info, list) else (
|
|
1464
|
+
self.ctx.analyzer.registry.get_function(local_name))
|
|
1465
|
+
# For C-linkage functions, emit an extern "C" re-declaration.
|
|
1466
|
+
# Using/alias re-exports don't work because the C++ name may
|
|
1467
|
+
# differ from the Python name (e.g., @native("SDL_GetTicks", binding="C") def get_ticks).
|
|
1468
|
+
# extern "C" re-decls don't reach into a sub-namespace, so
|
|
1469
|
+
# the sibling-cycle suppression below does not apply here.
|
|
1470
|
+
if func_infos and (func_infos[0].is_native_c or func_infos[0].is_extern_c):
|
|
1471
|
+
self.functions.gen_extern_c_redecl(hpp, func_infos[0])
|
|
1472
|
+
any_written = True
|
|
1473
|
+
continue
|
|
1474
|
+
# Skip decorator stubs and native/template functions
|
|
1475
|
+
if func_infos and all(
|
|
1476
|
+
fi.is_decorator_stub or fi.native_name or fi.cpp_template or fi.special_handling
|
|
1477
|
+
for fi in func_infos
|
|
1478
|
+
):
|
|
1479
|
+
continue
|
|
1480
|
+
# Sibling cycle: the same shape as the cycle_peers check
|
|
1481
|
+
# above, induced by the parent-walk in
|
|
1482
|
+
# `_write_header_preamble` for descendant submodules.
|
|
1483
|
+
# Skipped only after the extern "C" re-decl branch since
|
|
1484
|
+
# that one doesn't reach into the sub-namespace.
|
|
1485
|
+
if self._is_descendant_submodule(source_module):
|
|
1486
|
+
continue
|
|
1487
|
+
qualified = qualified_cpp_name(source_module, original_name)
|
|
1488
|
+
if local_name == original_name:
|
|
1489
|
+
hpp.write(f"using {qualified};\n")
|
|
1490
|
+
else:
|
|
1491
|
+
hpp.write(f"inline auto& {local_name} = {qualified};\n")
|
|
1492
|
+
any_written = True
|
|
1493
|
+
if any_written:
|
|
1494
|
+
hpp.write("\n")
|
|
1495
|
+
|
|
1496
|
+
# Re-exported records / enums. Skip nested types (`.` in name): a
|
|
1497
|
+
# `using ::ns::Container::Inner;` at namespace scope is illegal C++
|
|
1498
|
+
# (using-declaration for a member at non-class scope); the inner
|
|
1499
|
+
# type is accessible through the outer's `using`. Also skip natives
|
|
1500
|
+
# / @builtin_type stubs (no namespace declaration to point at).
|
|
1501
|
+
# Enums also suppress cycle-peer re-exports (revisitable -- the
|
|
1502
|
+
# forward-declared enum in `<peer>_fwd.hpp` would in principle
|
|
1503
|
+
# be `using`'d safely).
|
|
1504
|
+
def _record_skip(info: object, name: str) -> bool:
|
|
1505
|
+
ri = info if isinstance(info, RecordInfo) else (
|
|
1506
|
+
self.analyzer.registry.get_record(name))
|
|
1507
|
+
return ri is not None and (ri.is_native or ri.is_keyword_stub)
|
|
1508
|
+
|
|
1509
|
+
def _enum_skip(info: object, name: str) -> bool:
|
|
1510
|
+
et = info if info is not None else (
|
|
1511
|
+
self.analyzer.registry.get_enum(name))
|
|
1512
|
+
einfo = enum_info_of(et) if et is not None else None
|
|
1513
|
+
return einfo is not None and einfo.is_native
|
|
1514
|
+
|
|
1515
|
+
# Mirrors `protocol_reexports_external` below; downstream record
|
|
1516
|
+
# references already use the fully-qualified `::tpystd::X::Foo`
|
|
1517
|
+
# form, so the unqualified alias would only matter inside this
|
|
1518
|
+
# header and fails the cyclic-include ordering check.
|
|
1519
|
+
record_reexports_external = [
|
|
1520
|
+
e for e in record_reexports
|
|
1521
|
+
if e[1] not in self.ctx.implicit_stdlib_modules
|
|
1522
|
+
]
|
|
1523
|
+
# When this module is a package re-exporting records/enums from
|
|
1524
|
+
# a descendant submodule, the sub's header pulls in our header
|
|
1525
|
+
# via the parent-walk (`_write_header_preamble`), so the
|
|
1526
|
+
# following `using ::cur::sub::Name;` lines would otherwise
|
|
1527
|
+
# resolve against a not-yet-opened `cur::sub` namespace whenever
|
|
1528
|
+
# the sub is compiled first. Forward-declare those types in
|
|
1529
|
+
# their (relative) sub-namespace so the using-decls resolve at
|
|
1530
|
+
# any include order. Iterate the implicit-stdlib-filtered list
|
|
1531
|
+
# so we don't fwd-decl entries the emit-block will drop anyway.
|
|
1532
|
+
self._emit_sibling_submodule_fwd_decls(
|
|
1533
|
+
hpp, record_reexports_external, enum_reexports,
|
|
1534
|
+
_record_skip, _enum_skip)
|
|
1535
|
+
self._emit_alias_using_block(hpp, record_reexports_external, _record_skip)
|
|
1536
|
+
|
|
1537
|
+
# Re-exported protocols (@dynamic generates an abstract base
|
|
1538
|
+
# class with the protocol's clean name; @static protocols
|
|
1539
|
+
# generate a C++ concept. Either way, a `using` declaration
|
|
1540
|
+
# makes the name accessible in the consuming module's scope so
|
|
1541
|
+
# references like `Box[SomeProto]` resolve at the C++ level.
|
|
1542
|
+
# Native marker protocols (NativeIterable, ValueType, etc.)
|
|
1543
|
+
# don't define a concept at the user-facing name and skip the
|
|
1544
|
+
# `using` -- consumers reach them through `tpy::` qualification.
|
|
1545
|
+
def _protocol_skip(info: object, name: str) -> bool:
|
|
1546
|
+
# info here is a ProtocolInfo (per symbol_binding); skip
|
|
1547
|
+
# native marker protocols (NativeIterable, ValueType, ...).
|
|
1548
|
+
# These map to C++ concepts at fixed ::tpy:: qnames and
|
|
1549
|
+
# don't have a TPy-generated using target.
|
|
1550
|
+
if isinstance(info, ProtocolInfo):
|
|
1551
|
+
return info.cpp_concept is not None
|
|
1552
|
+
return False
|
|
1553
|
+
|
|
1554
|
+
# Filter out implicit-stdlib sources: the stdlib stub build
|
|
1555
|
+
# (each module compiled standalone for caching) doesn't have
|
|
1556
|
+
# later-in-chain headers visible yet when the using-declaration
|
|
1557
|
+
# is processed. Tpy._core importing typing.Iterable etc. is the
|
|
1558
|
+
# canonical case -- those names work via full qualification in
|
|
1559
|
+
# downstream emit paths, so skipping the `using` is safe. The
|
|
1560
|
+
# cross-module case that DOES need the `using` is outside-stdlib
|
|
1561
|
+
# (e.g. asyncio importing protocols from a sibling submodule).
|
|
1562
|
+
# Descendant-submodule sources are NOT dropped here unlike the
|
|
1563
|
+
# function/variable paths above: a static protocol compiles to
|
|
1564
|
+
# a C++ concept (not forward-declarable), and the parent's own
|
|
1565
|
+
# .cpp emits bare protocol names in function signatures
|
|
1566
|
+
# (e.g. `Box<AnyTask>&&` in asyncio.cpp), relying on this
|
|
1567
|
+
# `using` to bring the name into scope. The cycle would only
|
|
1568
|
+
# actually fire if the source sub-module itself triggers the
|
|
1569
|
+
# parent-walk back via a dotted user-module import; that
|
|
1570
|
+
# narrower shape isn't reproduced in the test corpus today.
|
|
1571
|
+
protocol_reexports_external = [
|
|
1572
|
+
e for e in protocol_reexports
|
|
1573
|
+
if e[1] not in self.ctx.implicit_stdlib_modules
|
|
1574
|
+
]
|
|
1575
|
+
self._emit_alias_using_block(
|
|
1576
|
+
hpp, protocol_reexports_external, _protocol_skip,
|
|
1577
|
+
skip_cycle_peers=True)
|
|
1578
|
+
self._emit_alias_using_block(
|
|
1579
|
+
hpp, enum_reexports, _enum_skip, skip_cycle_peers=True)
|
|
1580
|
+
|
|
1581
|
+
# Re-exported variables. Skip:
|
|
1582
|
+
# - variables whose ultimate source is a native_module (no
|
|
1583
|
+
# namespace path; their use sites resolve via
|
|
1584
|
+
# ModuleVarInfo.native_cpp_name);
|
|
1585
|
+
# - variables sourced from a cycle peer (variables aren't
|
|
1586
|
+
# declared in `<peer>_fwd.hpp`, so a `using` would reach
|
|
1587
|
+
# into the peer's full header);
|
|
1588
|
+
# - variables sourced from a descendant submodule (the sub's
|
|
1589
|
+
# parent-walk pulls us back in, so the `using` would
|
|
1590
|
+
# resolve against a not-yet-opened sub namespace whenever
|
|
1591
|
+
# the sub is compiled first -- mirror of the function path
|
|
1592
|
+
# above). Safe to drop now that `default_to_cpp` routes
|
|
1593
|
+
# default-arg references through the defining module.
|
|
1594
|
+
if var_reexports:
|
|
1595
|
+
any_written = False
|
|
1596
|
+
for local_name, source_module, original_name, _info in var_reexports:
|
|
1597
|
+
if (source_module in self.ctx.cycle_peers
|
|
1598
|
+
or self._is_descendant_submodule(source_module)):
|
|
1599
|
+
continue
|
|
1600
|
+
src_info = self.analyzer.registry.get_module(source_module)
|
|
1601
|
+
if src_info is not None and src_info.is_native_module:
|
|
1602
|
+
continue
|
|
1603
|
+
qualified = qualified_cpp_name(source_module, original_name)
|
|
1604
|
+
hpp.write(f"inline auto& {local_name} = {qualified};\n")
|
|
1605
|
+
any_written = True
|
|
1606
|
+
if any_written:
|
|
1607
|
+
hpp.write("\n")
|
|
1608
|
+
|
|
1609
|
+
def _is_descendant_submodule(self, source_module: str) -> bool:
|
|
1610
|
+
"""True when `source_module` is a strict descendant of the
|
|
1611
|
+
current (package) module. Used to identify re-exports that
|
|
1612
|
+
induce a parent<->sub header cycle: the sub's parent-walk
|
|
1613
|
+
emits our header first, so any `using ::cur::sub::Name;`
|
|
1614
|
+
we emit inside our namespace runs while `cur::sub` is mid-
|
|
1615
|
+
parsing and hasn't opened yet.
|
|
1616
|
+
"""
|
|
1617
|
+
cur = self.ctx.module_name
|
|
1618
|
+
return source_module.startswith(cur + ".")
|
|
1619
|
+
|
|
1620
|
+
def _emit_sibling_submodule_fwd_decls(
|
|
1621
|
+
self, hpp: TextIO,
|
|
1622
|
+
record_entries: list[tuple[str, str, str, object]],
|
|
1623
|
+
enum_entries: list[tuple[str, str, str, object]],
|
|
1624
|
+
record_skip: Callable[[object, str], bool],
|
|
1625
|
+
enum_skip: Callable[[object, str], bool],
|
|
1626
|
+
) -> None:
|
|
1627
|
+
"""Emit forward declarations of records/enums re-exported from
|
|
1628
|
+
descendant submodules, grouped by relative sub-namespace path.
|
|
1629
|
+
Lives inside our own namespace block, so relative names like
|
|
1630
|
+
`namespace sub { struct X; }` resolve to `cur::sub::X` and
|
|
1631
|
+
satisfy the using-decls emitted just after this block. Skip
|
|
1632
|
+
filters mirror the using-decl emitter so we don't fwd-decl
|
|
1633
|
+
natives, keyword stubs, or nested-name types that the
|
|
1634
|
+
using-decl emitter would also reject.
|
|
1635
|
+
"""
|
|
1636
|
+
cur = self.ctx.module_name
|
|
1637
|
+
by_subpath: dict[str, list[str]] = {}
|
|
1638
|
+
|
|
1639
|
+
def add(entry, kind: str, skip_fn: Callable[[object, str], bool]) -> None:
|
|
1640
|
+
local_name, source_module, original_name, info = entry
|
|
1641
|
+
if not self._is_descendant_submodule(source_module):
|
|
1642
|
+
return
|
|
1643
|
+
if "." in local_name or "." in original_name:
|
|
1644
|
+
return
|
|
1645
|
+
if skip_fn(info, original_name):
|
|
1646
|
+
return
|
|
1647
|
+
subpath = source_module[len(cur) + 1:].replace(".", "::")
|
|
1648
|
+
if kind == "record":
|
|
1649
|
+
ri = info if isinstance(info, RecordInfo) else (
|
|
1650
|
+
self.analyzer.registry.get_record(original_name))
|
|
1651
|
+
if ri is not None and ri.type_params:
|
|
1652
|
+
# Class template: mirror the full template header from
|
|
1653
|
+
# `gen_record_template_header` so the forward decl
|
|
1654
|
+
# agrees with the sub-header's declaration.
|
|
1655
|
+
header = self.protocols.gen_record_template_header(
|
|
1656
|
+
ri.type_params, ri.type_param_bounds,
|
|
1657
|
+
ri.type_param_kinds)
|
|
1658
|
+
by_subpath.setdefault(subpath, []).append(
|
|
1659
|
+
f"{header} struct {original_name};")
|
|
1660
|
+
else:
|
|
1661
|
+
by_subpath.setdefault(subpath, []).append(
|
|
1662
|
+
f"struct {original_name};")
|
|
1663
|
+
else:
|
|
1664
|
+
# `enum class Name : underlying;` -- underlying must
|
|
1665
|
+
# match the definition. Resolve via the registered
|
|
1666
|
+
# NominalType (same source the `<peer>_fwd.hpp` path
|
|
1667
|
+
# uses in `generate_fwd_header`).
|
|
1668
|
+
et = info if info is not None else (
|
|
1669
|
+
self.analyzer.registry.get_enum(original_name))
|
|
1670
|
+
einfo = enum_info_of(et) if et is not None else None
|
|
1671
|
+
underlying = (einfo.underlying_type.to_cpp()
|
|
1672
|
+
if einfo is not None else "int32_t")
|
|
1673
|
+
by_subpath.setdefault(subpath, []).append(
|
|
1674
|
+
f"enum class {original_name} : {underlying};")
|
|
1675
|
+
|
|
1676
|
+
for entry in record_entries:
|
|
1677
|
+
add(entry, "record", record_skip)
|
|
1678
|
+
for entry in enum_entries:
|
|
1679
|
+
add(entry, "enum", enum_skip)
|
|
1680
|
+
|
|
1681
|
+
if not by_subpath:
|
|
1682
|
+
return
|
|
1683
|
+
for subpath in sorted(by_subpath):
|
|
1684
|
+
decls = " ".join(by_subpath[subpath])
|
|
1685
|
+
hpp.write(f"namespace {subpath} {{ {decls} }}\n")
|
|
1686
|
+
hpp.write("\n")
|
|
1687
|
+
|
|
1688
|
+
def _emit_alias_using_block(
|
|
1689
|
+
self, hpp: TextIO,
|
|
1690
|
+
entries: list[tuple[str, str, str, object]],
|
|
1691
|
+
skip: Callable[[object, str], bool],
|
|
1692
|
+
*, skip_cycle_peers: bool = False,
|
|
1693
|
+
) -> None:
|
|
1694
|
+
"""Emit `using ::ns::Foo;` (or `using Local = ::ns::Foo;`) for
|
|
1695
|
+
each entry in `entries`, skipping nested types and entries the
|
|
1696
|
+
predicate rejects. Entries are `(local_name, source_module,
|
|
1697
|
+
original_name, info)` tuples sourced from the bucketing pass in
|
|
1698
|
+
`_gen_reexport_using_decls`. Trailing blank line if anything
|
|
1699
|
+
was written.
|
|
1700
|
+
"""
|
|
1701
|
+
if not entries:
|
|
1702
|
+
return
|
|
1703
|
+
any_written = False
|
|
1704
|
+
for local_name, source_module, original_name, info in entries:
|
|
1705
|
+
if skip_cycle_peers and source_module in self.ctx.cycle_peers:
|
|
1706
|
+
continue
|
|
1707
|
+
if "." in local_name or "." in original_name:
|
|
1708
|
+
continue
|
|
1709
|
+
if skip(info, original_name):
|
|
1710
|
+
continue
|
|
1711
|
+
qualified = qualified_cpp_name(source_module, original_name)
|
|
1712
|
+
if local_name == original_name:
|
|
1713
|
+
hpp.write(f"using {qualified};\n")
|
|
1714
|
+
else:
|
|
1715
|
+
hpp.write(f"using {local_name} = {qualified};\n")
|
|
1716
|
+
any_written = True
|
|
1717
|
+
if any_written:
|
|
1718
|
+
hpp.write("\n")
|
|
1719
|
+
|
|
1720
|
+
def _gen_recursive_union_struct(
|
|
1721
|
+
self, out: TextIO, name: str, typ: UnionType,
|
|
1722
|
+
type_params: 'list[str] | None' = None,
|
|
1723
|
+
type_param_kinds: 'list | None' = None,
|
|
1724
|
+
) -> None:
|
|
1725
|
+
"""Generate a wrapper struct for a recursive union type alias.
|
|
1726
|
+
|
|
1727
|
+
Non-generic (`type JsonValue = ... | list[JsonValue]`):
|
|
1728
|
+
struct JsonValue {
|
|
1729
|
+
using variant_type = std::variant<...>;
|
|
1730
|
+
variant_type value;
|
|
1731
|
+
JsonValue() = default;
|
|
1732
|
+
template<typename T> requires ... JsonValue(T&& v) : value(...) {}
|
|
1733
|
+
bool operator==(const JsonValue&) const = default;
|
|
1734
|
+
};
|
|
1735
|
+
|
|
1736
|
+
Generic (`type Tree[T] = T | list[Tree[T]]`): one template per alias
|
|
1737
|
+
name. The default ctor / `operator==` are constrained so an
|
|
1738
|
+
instantiation with a non-default-constructible / non-comparable T
|
|
1739
|
+
loses just that member (clean error at the use site) rather than an
|
|
1740
|
+
ill-formed struct. The forwarding ctor's template param uses a
|
|
1741
|
+
sentinel name that cannot collide with a user type param.
|
|
1742
|
+
"""
|
|
1743
|
+
cpp_members = [
|
|
1744
|
+
"std::monostate" if is_void_like_type(m) else self.types.type_to_cpp(m)
|
|
1745
|
+
for m in typ.members
|
|
1746
|
+
]
|
|
1747
|
+
variant_type = f"std::variant<{', '.join(cpp_members)}>"
|
|
1748
|
+
if type_params:
|
|
1749
|
+
header = self.protocols.gen_record_template_header(
|
|
1750
|
+
type_params, {}, type_param_kinds or [])
|
|
1751
|
+
out.write(f"{header}\n")
|
|
1752
|
+
out.write(f"struct {name} {{\n")
|
|
1753
|
+
out.write(f" using variant_type = {variant_type};\n")
|
|
1754
|
+
out.write(f" variant_type value;\n\n")
|
|
1755
|
+
out.write(f" {name}() requires std::default_initializable<variant_type> = default;\n")
|
|
1756
|
+
out.write(f" template<typename _TpyAliasCtorArg>\n")
|
|
1757
|
+
out.write(f" requires std::constructible_from<variant_type, _TpyAliasCtorArg&&>\n")
|
|
1758
|
+
out.write(f" {name}(_TpyAliasCtorArg&& v) : value(std::forward<_TpyAliasCtorArg>(v)) {{}}\n\n")
|
|
1759
|
+
out.write(f" bool operator==(const {name}&) const "
|
|
1760
|
+
f"requires std::equality_comparable<variant_type> = default;\n\n")
|
|
1761
|
+
out.write(f" friend std::ostream& operator<<(std::ostream& os, const {name}& v) {{\n")
|
|
1762
|
+
out.write(f" ::tpy::detail::print_element(os, v.value);\n")
|
|
1763
|
+
out.write(f" return os;\n")
|
|
1764
|
+
out.write(f" }}\n")
|
|
1765
|
+
out.write(f"}};\n")
|
|
1766
|
+
return
|
|
1767
|
+
out.write(f"struct {name} {{\n")
|
|
1768
|
+
out.write(f" using variant_type = {variant_type};\n")
|
|
1769
|
+
out.write(f" variant_type value;\n\n")
|
|
1770
|
+
out.write(f" {name}() = default;\n")
|
|
1771
|
+
out.write(f" template<typename T>\n")
|
|
1772
|
+
out.write(f" requires std::constructible_from<variant_type, T&&>\n")
|
|
1773
|
+
out.write(f" {name}(T&& v) : value(std::forward<T>(v)) {{}}\n\n")
|
|
1774
|
+
out.write(f" bool operator==(const {name}&) const = default;\n\n")
|
|
1775
|
+
out.write(f" friend std::ostream& operator<<(std::ostream& os, const {name}& v) {{\n")
|
|
1776
|
+
out.write(f" ::tpy::detail::print_element(os, v.value);\n")
|
|
1777
|
+
out.write(f" return os;\n")
|
|
1778
|
+
out.write(f" }}\n")
|
|
1779
|
+
out.write(f"}};\n")
|
|
1780
|
+
|
|
1781
|
+
def _gen_enum_decl(self, out: TextIO, enum) -> None:
|
|
1782
|
+
"""Generate C++ enum class declaration (no helpers)."""
|
|
1783
|
+
enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
|
|
1784
|
+
if not enum_type:
|
|
1785
|
+
return
|
|
1786
|
+
underlying = enum_info_of(enum_type).underlying_type.to_cpp()
|
|
1787
|
+
|
|
1788
|
+
out.write(f"enum class {enum.name} : {underlying} {{\n")
|
|
1789
|
+
for member_name, value, _ in enum.members:
|
|
1790
|
+
out.write(f" {member_name} = {value},\n")
|
|
1791
|
+
out.write("};\n\n")
|
|
1792
|
+
|
|
1793
|
+
def _gen_dynamic_adapter_specs(self, out: TextIO, module: TpyModule,
|
|
1794
|
+
dynamic_protocols: list) -> None:
|
|
1795
|
+
"""Generate ::tpy::Adapter/RefAdapter partial specializations (global scope)."""
|
|
1796
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
1797
|
+
# User-namespace type names that must be qualified inside `tpy::`
|
|
1798
|
+
# adapter overrides; see ProtocolGenerator._qualify_user_types.
|
|
1799
|
+
user_type_names = {r.name for r in module.records} | {p.name for p in module.protocols}
|
|
1800
|
+
for protocol in dynamic_protocols:
|
|
1801
|
+
self.protocols.gen_dynamic_adapter_specs(out, protocol, ns, user_type_names)
|
|
1802
|
+
out.write("\n")
|
|
1803
|
+
|
|
1804
|
+
def _gen_enum_util_decls_for(self, out: TextIO, enums: list) -> None:
|
|
1805
|
+
"""Generate ::tpy::EnumUtil<E> specialization declarations for given enums."""
|
|
1806
|
+
from .context import enum_cpp_name
|
|
1807
|
+
cur_module = self.ctx.module_name
|
|
1808
|
+
for enum in enums:
|
|
1809
|
+
enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
|
|
1810
|
+
if not enum_type:
|
|
1811
|
+
continue
|
|
1812
|
+
underlying = enum_info_of(enum_type).underlying_type.to_cpp()
|
|
1813
|
+
qualified = enum_cpp_name(enum_type, cur_module, absolute=True)
|
|
1814
|
+
short_name = bare_name(enum.name)
|
|
1815
|
+
member_count = len(enum.members)
|
|
1816
|
+
out.write(f"template<>\n")
|
|
1817
|
+
out.write(f"struct tpy::EnumUtil<{qualified}> {{\n")
|
|
1818
|
+
out.write(f" static constexpr std::string_view type_name = \"{short_name}\";\n")
|
|
1819
|
+
out.write(f" static std::string_view name({qualified} e);\n")
|
|
1820
|
+
out.write(f" static const std::array<{qualified}, {member_count}> members;\n")
|
|
1821
|
+
out.write(f" static {qualified} from_value({underlying} v);\n")
|
|
1822
|
+
out.write(f" static {qualified} from_name(std::string_view s);\n")
|
|
1823
|
+
out.write(f" static std::optional<{qualified}> try_parse(std::string_view s);\n")
|
|
1824
|
+
out.write(f"}};\n\n")
|
|
1825
|
+
|
|
1826
|
+
def _gen_enum_operator_ostream(self, out: TextIO, enum) -> None:
|
|
1827
|
+
"""Generate inline operator<< inside user namespace."""
|
|
1828
|
+
cpp_name = enum.name.replace(".", "::")
|
|
1829
|
+
# Use short name for repr to match CPython (Kind.TEXT, not Message.Kind.TEXT)
|
|
1830
|
+
short_name = bare_name(enum.name)
|
|
1831
|
+
out.write(f"inline std::ostream& operator<<(std::ostream& __os, {cpp_name} __e) {{\n")
|
|
1832
|
+
out.write(f" return __os << \"{short_name}.\" << ::tpy::EnumUtil<{cpp_name}>::name(__e);\n")
|
|
1833
|
+
out.write(f"}}\n\n")
|
|
1834
|
+
|
|
1835
|
+
def _gen_enum_source_defs(self, out: TextIO, module: TpyModule) -> None:
|
|
1836
|
+
"""Generate ::tpy::EnumUtil<E> member definitions in namespace tpy."""
|
|
1837
|
+
from .context import enum_cpp_name
|
|
1838
|
+
cur_module = self.ctx.module_name
|
|
1839
|
+
out.write("namespace tpy {\n\n")
|
|
1840
|
+
for enum in module.all_enums():
|
|
1841
|
+
enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
|
|
1842
|
+
if not enum_type:
|
|
1843
|
+
continue
|
|
1844
|
+
einfo = enum_info_of(enum_type)
|
|
1845
|
+
underlying = einfo.underlying_type.to_cpp()
|
|
1846
|
+
qualified = enum_cpp_name(enum_type, cur_module, absolute=True)
|
|
1847
|
+
short_name = bare_name(enum.name)
|
|
1848
|
+
member_count = len(enum.members)
|
|
1849
|
+
# cpp_of(python_name) -> C++ enumerator name (Python name if no
|
|
1850
|
+
# native_member() override). Used at every site that emits a
|
|
1851
|
+
# `qualified::<member>` C++ symbol reference. Sites that emit
|
|
1852
|
+
# the Python-side string (name() return value, try_parse key)
|
|
1853
|
+
# keep the Python name. Built once per enum to avoid O(N**2)
|
|
1854
|
+
# over the per-member emission loops.
|
|
1855
|
+
_cpp_map = einfo.cpp_member_name_map
|
|
1856
|
+
def cpp_of(n: str, _m: dict[str, str] = _cpp_map) -> str:
|
|
1857
|
+
return _m.get(n, n)
|
|
1858
|
+
|
|
1859
|
+
# @native enum + explicit values: pin each declared value to the
|
|
1860
|
+
# C++ side at compile time. Lives in the .cpp (not the .hpp) so it
|
|
1861
|
+
# doesn't bloat every translation unit that includes the header --
|
|
1862
|
+
# the assertion still fires at the same point, when this .cpp
|
|
1863
|
+
# compiles. auto()/native_member() opt out -- the user did not
|
|
1864
|
+
# declare a value.
|
|
1865
|
+
if enum.is_native and enum.has_explicit_values:
|
|
1866
|
+
for member_name, value, _ in enum.members:
|
|
1867
|
+
out.write(
|
|
1868
|
+
f"static_assert(static_cast<{underlying}>({qualified}::{cpp_of(member_name)}) == {value}, "
|
|
1869
|
+
f"\"TPy-declared value for {short_name}.{member_name} does not match C++ side\");\n"
|
|
1870
|
+
)
|
|
1871
|
+
out.write("\n")
|
|
1872
|
+
|
|
1873
|
+
# name()
|
|
1874
|
+
out.write(f"std::string_view EnumUtil<{qualified}>::name({qualified} __e) {{\n")
|
|
1875
|
+
out.write(f" switch (__e) {{\n")
|
|
1876
|
+
for member_name, _, _ in enum.members:
|
|
1877
|
+
out.write(
|
|
1878
|
+
f" case {qualified}::{cpp_of(member_name)}: "
|
|
1879
|
+
f"return \"{member_name}\";\n"
|
|
1880
|
+
)
|
|
1881
|
+
# Internal invariant: TPy enum values are always one of the declared
|
|
1882
|
+
# cases, so the default is unreachable from well-typed code. Stays
|
|
1883
|
+
# panic (not raise<ValueError>) -- the from_value path below already
|
|
1884
|
+
# raises ValueError for user-supplied invalid values.
|
|
1885
|
+
out.write(f" default: tpy_panic(\"invalid enum value\");\n")
|
|
1886
|
+
out.write(f" }}\n")
|
|
1887
|
+
out.write(f"}}\n\n")
|
|
1888
|
+
|
|
1889
|
+
# members
|
|
1890
|
+
out.write(f"const std::array<{qualified}, {member_count}>\n")
|
|
1891
|
+
out.write(f"EnumUtil<{qualified}>::members = {{\n")
|
|
1892
|
+
for member_name, _, _ in enum.members:
|
|
1893
|
+
out.write(f" {qualified}::{cpp_of(member_name)},\n")
|
|
1894
|
+
out.write(f"}};\n\n")
|
|
1895
|
+
|
|
1896
|
+
# from_value(). For @native enums the C++ side is the source
|
|
1897
|
+
# of truth for member values, so cases key off the C++
|
|
1898
|
+
# enumerator's actual value (not the TPy-declared int, which
|
|
1899
|
+
# may be `auto()`-assigned and meaningless for the binding).
|
|
1900
|
+
out.write(f"{qualified} EnumUtil<{qualified}>::from_value({underlying} __v) {{\n")
|
|
1901
|
+
out.write(f" switch (__v) {{\n")
|
|
1902
|
+
for member_name, value, _ in enum.members:
|
|
1903
|
+
if enum.is_native:
|
|
1904
|
+
out.write(
|
|
1905
|
+
f" case static_cast<{underlying}>({qualified}::{cpp_of(member_name)}): "
|
|
1906
|
+
f"return {qualified}::{cpp_of(member_name)};\n"
|
|
1907
|
+
)
|
|
1908
|
+
else:
|
|
1909
|
+
out.write(f" case {value}: return {qualified}::{member_name};\n")
|
|
1910
|
+
out.write(f" default: raise<ValueError>(\"{{}} is not a valid {enum.name}\", __v);\n")
|
|
1911
|
+
out.write(f" }}\n")
|
|
1912
|
+
out.write(f"}}\n\n")
|
|
1913
|
+
|
|
1914
|
+
# try_parse(): keys are Python-side names (user-facing), returns
|
|
1915
|
+
# are C++ enumerators (which may differ for @native enums).
|
|
1916
|
+
out.write(f"std::optional<{qualified}> EnumUtil<{qualified}>::try_parse(std::string_view __name) {{\n")
|
|
1917
|
+
member_names = [name for name, _, _ in enum.members]
|
|
1918
|
+
if len(member_names) >= STRING_SWITCH_THRESHOLD:
|
|
1919
|
+
self._gen_enum_try_parse_switch(out, qualified, member_names, cpp_of)
|
|
1920
|
+
else:
|
|
1921
|
+
for member_name in member_names:
|
|
1922
|
+
out.write(
|
|
1923
|
+
f" if (__name == \"{member_name}\") "
|
|
1924
|
+
f"return {qualified}::{cpp_of(member_name)};\n"
|
|
1925
|
+
)
|
|
1926
|
+
out.write(f" return std::nullopt;\n")
|
|
1927
|
+
out.write(f"}}\n\n")
|
|
1928
|
+
|
|
1929
|
+
# from_name() delegates to try_parse()
|
|
1930
|
+
out.write(f"{qualified} EnumUtil<{qualified}>::from_name(std::string_view __name) {{\n")
|
|
1931
|
+
out.write(f" auto __result = try_parse(__name);\n")
|
|
1932
|
+
out.write(f" if (!__result.has_value()) raise<KeyError>(\"{{}}\", __name);\n")
|
|
1933
|
+
out.write(f" return *__result;\n")
|
|
1934
|
+
out.write(f"}}\n\n")
|
|
1935
|
+
|
|
1936
|
+
out.write("} // namespace tpy\n\n")
|
|
1937
|
+
|
|
1938
|
+
@staticmethod
|
|
1939
|
+
def _gen_enum_try_parse_switch(
|
|
1940
|
+
out: TextIO, qualified: str,
|
|
1941
|
+
member_names: list[str],
|
|
1942
|
+
cpp_of: Callable[[str], str],
|
|
1943
|
+
) -> None:
|
|
1944
|
+
"""Generate switch-based try_parse for enum with many members.
|
|
1945
|
+
|
|
1946
|
+
`cpp_of` maps Python-side member names to C++ enumerator names
|
|
1947
|
+
(identity for tpy-defined enums; honors native_member() overrides
|
|
1948
|
+
for @native enums). Members are keyed by Python name (user-facing
|
|
1949
|
+
string), returned as their C++ enumerator.
|
|
1950
|
+
"""
|
|
1951
|
+
kind, param, _buckets = find_best_discriminator(member_names)
|
|
1952
|
+
|
|
1953
|
+
# Build bucket -> [member_name] mapping
|
|
1954
|
+
buckets: dict[int, list[str]] = {}
|
|
1955
|
+
for name in member_names:
|
|
1956
|
+
key = len(name) if kind == "length" else ord(name[param])
|
|
1957
|
+
buckets.setdefault(key, []).append(name)
|
|
1958
|
+
|
|
1959
|
+
if kind == "length":
|
|
1960
|
+
out.write(f" switch (__name.size()) {{\n")
|
|
1961
|
+
case_indent = " "
|
|
1962
|
+
body_indent = " "
|
|
1963
|
+
else:
|
|
1964
|
+
out.write(f" if (__name.size() >= {param + 1}) {{\n")
|
|
1965
|
+
out.write(f" switch (static_cast<unsigned char>(__name[{param}])) {{\n")
|
|
1966
|
+
case_indent = " "
|
|
1967
|
+
body_indent = " "
|
|
1968
|
+
|
|
1969
|
+
for disc_value in sorted(buckets.keys()):
|
|
1970
|
+
names = buckets[disc_value]
|
|
1971
|
+
if kind == "char_at":
|
|
1972
|
+
ch = chr(disc_value)
|
|
1973
|
+
out.write(f"{case_indent}case '{escape_cpp_char(ch)}': {{\n")
|
|
1974
|
+
else:
|
|
1975
|
+
out.write(f"{case_indent}case {disc_value}: {{\n")
|
|
1976
|
+
for name in names:
|
|
1977
|
+
out.write(
|
|
1978
|
+
f"{body_indent}if (__name == \"{name}\") "
|
|
1979
|
+
f"return {qualified}::{cpp_of(name)};\n"
|
|
1980
|
+
)
|
|
1981
|
+
out.write(f"{body_indent}break;\n")
|
|
1982
|
+
out.write(f"{case_indent}}}\n")
|
|
1983
|
+
|
|
1984
|
+
out.write(f"{case_indent}}}\n")
|
|
1985
|
+
if kind == "char_at":
|
|
1986
|
+
out.write(f" }}\n")
|
|
1987
|
+
|
|
1988
|
+
def _module_to_include_path(self, module_name: str, *,
|
|
1989
|
+
prefer_fwd: bool = False) -> str:
|
|
1990
|
+
"""Convert dotted module name to include path.
|
|
1991
|
+
|
|
1992
|
+
When `prefer_fwd` is True AND `module_name` is a cycle peer of
|
|
1993
|
+
the current module, returns `<mod>_fwd.hpp` instead of
|
|
1994
|
+
`<mod>.hpp`. The fwd header carries forward declarations of
|
|
1995
|
+
the peer's records / enums / @dynamic protocols so the cyclic
|
|
1996
|
+
include resolves without requiring complete types.
|
|
1997
|
+
"""
|
|
1998
|
+
from .context import module_to_include_path
|
|
1999
|
+
path = module_to_include_path(module_name)
|
|
2000
|
+
if prefer_fwd and module_name in self.ctx.cycle_peers:
|
|
2001
|
+
if path.endswith(".hpp"):
|
|
2002
|
+
return path[:-len(".hpp")] + "_fwd.hpp"
|
|
2003
|
+
return path
|
|
2004
|
+
|
|
2005
|
+
def _write_header_preamble(self, out: TextIO,
|
|
2006
|
+
native_funcs: list[TpyFunction] | None = None,
|
|
2007
|
+
native_globals: list[TpyVarDecl] | None = None,
|
|
2008
|
+
directives: ModuleDirectives | None = None) -> None:
|
|
2009
|
+
out.write("// Generated by TurboPython Compiler\n")
|
|
2010
|
+
out.write("#pragma once\n\n")
|
|
2011
|
+
out.write('#include <tpy/tpy.hpp>\n')
|
|
2012
|
+
included: set[str] = set()
|
|
2013
|
+
if directives and directives.includes:
|
|
2014
|
+
for include_path, platform in directives.includes:
|
|
2015
|
+
if not _platform_matches(platform):
|
|
2016
|
+
continue
|
|
2017
|
+
if include_path.startswith('<') and include_path.endswith('>'):
|
|
2018
|
+
out.write(f'#include {include_path}\n')
|
|
2019
|
+
else:
|
|
2020
|
+
out.write(f'#include "{include_path}"\n')
|
|
2021
|
+
included.add(include_path)
|
|
2022
|
+
out.write('\n')
|
|
2023
|
+
# Skip implicit stdlib peers we don't actually depend on -- auto-
|
|
2024
|
+
# including unrelated peers creates cycles when two stdlib modules
|
|
2025
|
+
# cross-reference each other. Strip self-parent prefixes because
|
|
2026
|
+
# qname-derived reach collapses `tpy.Foo` to "tpy" even for types
|
|
2027
|
+
# defined in this module.
|
|
2028
|
+
own_deps = set(self.ctx.user_module_imports) | set(self.analyzer.ctx.reached)
|
|
2029
|
+
own_parents = {
|
|
2030
|
+
".".join(self.ctx.module_name.split(".")[:i + 1])
|
|
2031
|
+
for i in range(self.ctx.module_name.count("."))
|
|
2032
|
+
}
|
|
2033
|
+
own_parents.add(self.ctx.module_name)
|
|
2034
|
+
own_deps -= own_parents
|
|
2035
|
+
# Closed-under-prefixes set of deps: dep `tpy._core._types` makes
|
|
2036
|
+
# peer `tpy._core` match (peer is an ancestor of a dep). Direction
|
|
2037
|
+
# 1 (peer descends from a dep) still keys on `own_deps` itself --
|
|
2038
|
+
# NOT this closure -- because parent-stripping above removed
|
|
2039
|
+
# `own_parents` from `own_deps` to suppress self-refs, and a
|
|
2040
|
+
# full prefix-closure would re-add them.
|
|
2041
|
+
dep_prefix_closure = set(own_deps)
|
|
2042
|
+
for dep in own_deps:
|
|
2043
|
+
dep_parts = dep.split(".")
|
|
2044
|
+
for i in range(1, len(dep_parts)):
|
|
2045
|
+
dep_prefix_closure.add(".".join(dep_parts[:i]))
|
|
2046
|
+
|
|
2047
|
+
def _dep_match(peer: str) -> bool:
|
|
2048
|
+
if peer in dep_prefix_closure:
|
|
2049
|
+
return True
|
|
2050
|
+
peer_parts = peer.split(".")
|
|
2051
|
+
for i in range(1, len(peer_parts)):
|
|
2052
|
+
if ".".join(peer_parts[:i]) in own_deps:
|
|
2053
|
+
return True
|
|
2054
|
+
return False
|
|
2055
|
+
|
|
2056
|
+
for implicit_mod in sorted(self.ctx.implicit_stdlib_modules):
|
|
2057
|
+
if implicit_mod == self.ctx.module_name:
|
|
2058
|
+
continue
|
|
2059
|
+
mod_info = self.analyzer.registry.get_module(implicit_mod)
|
|
2060
|
+
if mod_info and not mod_info.generates_header:
|
|
2061
|
+
continue
|
|
2062
|
+
if not _dep_match(implicit_mod):
|
|
2063
|
+
continue
|
|
2064
|
+
if implicit_mod in self.ctx.all_user_modules:
|
|
2065
|
+
include_path = self._module_to_include_path(implicit_mod)
|
|
2066
|
+
out.write(f'#include "{include_path}"\n')
|
|
2067
|
+
included.add(implicit_mod)
|
|
2068
|
+
# Include user module headers (including parent packages for dotted imports).
|
|
2069
|
+
# Driven by the union of user_module_imports (today's literal-import set)
|
|
2070
|
+
# and module_reached (types referenced through field/method chains).
|
|
2071
|
+
# For native modules we recurse through their own reach so that
|
|
2072
|
+
# # tpy: include() directives flow across native-to-native chains --
|
|
2073
|
+
# natives have no .hpp to chain through, so the consumer must emit
|
|
2074
|
+
# them all directly.
|
|
2075
|
+
chase_visited: set[str] = set()
|
|
2076
|
+
|
|
2077
|
+
def emit_native_includes(info) -> None:
|
|
2078
|
+
for inc, platform in info.includes:
|
|
2079
|
+
if not _platform_matches(platform):
|
|
2080
|
+
continue
|
|
2081
|
+
if inc in included:
|
|
2082
|
+
continue
|
|
2083
|
+
if inc.startswith('<') and inc.endswith('>'):
|
|
2084
|
+
out.write(f'#include {inc}\n')
|
|
2085
|
+
else:
|
|
2086
|
+
out.write(f'#include "{inc}"\n')
|
|
2087
|
+
included.add(inc)
|
|
2088
|
+
|
|
2089
|
+
def emit_non_native(mod_name: str) -> None:
|
|
2090
|
+
if mod_name == self.ctx.module_name or mod_name in included:
|
|
2091
|
+
return
|
|
2092
|
+
# Parent package headers first, for dotted modules
|
|
2093
|
+
parts = mod_name.split('.')
|
|
2094
|
+
for i in range(1, len(parts)):
|
|
2095
|
+
parent_pkg = '.'.join(parts[:i])
|
|
2096
|
+
if parent_pkg == self.ctx.module_name:
|
|
2097
|
+
continue
|
|
2098
|
+
if parent_pkg in self.ctx.all_user_modules and parent_pkg not in included:
|
|
2099
|
+
parent_info = self.analyzer.registry.get_module(parent_pkg)
|
|
2100
|
+
if parent_info is not None and not parent_info.generates_header:
|
|
2101
|
+
# Native-module package init has no .hpp; surface its
|
|
2102
|
+
# raw includes (if any) and mark as visited so we don't
|
|
2103
|
+
# try again for sibling submodules.
|
|
2104
|
+
emit_native_includes(parent_info)
|
|
2105
|
+
included.add(parent_pkg)
|
|
2106
|
+
continue
|
|
2107
|
+
out.write(f'#include "{self._module_to_include_path(parent_pkg, prefer_fwd=True)}"\n')
|
|
2108
|
+
included.add(parent_pkg)
|
|
2109
|
+
out.write(f'#include "{self._module_to_include_path(mod_name, prefer_fwd=True)}"\n')
|
|
2110
|
+
included.add(mod_name)
|
|
2111
|
+
|
|
2112
|
+
def visit(mod_name: str) -> None:
|
|
2113
|
+
if mod_name in chase_visited or mod_name == self.ctx.module_name:
|
|
2114
|
+
return
|
|
2115
|
+
chase_visited.add(mod_name)
|
|
2116
|
+
mod_info = self.analyzer.registry.get_module(mod_name)
|
|
2117
|
+
if mod_info is None:
|
|
2118
|
+
return
|
|
2119
|
+
if mod_info.is_builtin:
|
|
2120
|
+
return
|
|
2121
|
+
if not mod_info.generates_header:
|
|
2122
|
+
# Skip implicit stdlib native shims (decorator stubs, no C++)
|
|
2123
|
+
if mod_name in self.ctx.implicit_stdlib_modules:
|
|
2124
|
+
return
|
|
2125
|
+
emit_native_includes(mod_info)
|
|
2126
|
+
# Natives have no .hpp to chain through, so the consumer must
|
|
2127
|
+
# see every reach the native exposes -- recurse explicitly.
|
|
2128
|
+
for next_mod in sorted(mod_info.reached):
|
|
2129
|
+
visit(next_mod)
|
|
2130
|
+
return
|
|
2131
|
+
# Non-native: emit the hpp; preprocessor handles its transitive deps.
|
|
2132
|
+
emit_non_native(mod_name)
|
|
2133
|
+
|
|
2134
|
+
seed = set(self.ctx.user_module_imports)
|
|
2135
|
+
seed.update(self.analyzer.ctx.reached)
|
|
2136
|
+
for user_mod in sorted(seed):
|
|
2137
|
+
visit(user_mod)
|
|
2138
|
+
# Include macro dependency module headers (from MACRO_DEPS)
|
|
2139
|
+
for dep_mod in sorted(self.ctx.macro_dep_modules):
|
|
2140
|
+
if dep_mod in included or dep_mod == self.ctx.module_name:
|
|
2141
|
+
continue
|
|
2142
|
+
module_info = self.analyzer.registry.get_module(dep_mod)
|
|
2143
|
+
if module_info and (module_info.is_builtin or not module_info.generates_header):
|
|
2144
|
+
continue
|
|
2145
|
+
if dep_mod in self.ctx.all_user_modules:
|
|
2146
|
+
include_path = self._module_to_include_path(dep_mod)
|
|
2147
|
+
out.write(f'#include "{include_path}"\n')
|
|
2148
|
+
included.add(dep_mod)
|
|
2149
|
+
out.write("\n")
|
|
2150
|
+
|
|
2151
|
+
# Native global extern declarations go before the tpyapp namespace
|
|
2152
|
+
if native_globals:
|
|
2153
|
+
for stmt in native_globals:
|
|
2154
|
+
cpp_name = stmt.native_name or stmt.name
|
|
2155
|
+
var_type = resolve_stmt_type_cascade(stmt, self.analyzer, self.types)
|
|
2156
|
+
if stmt.linkage == VarLinkage.NATIVE_C_ARRAY:
|
|
2157
|
+
# C array global: Ptr[T] -> extern "C" T name[];
|
|
2158
|
+
# The incomplete array type decays to T* when used.
|
|
2159
|
+
if isinstance(var_type, PtrType):
|
|
2160
|
+
elem_cpp = var_type.pointee.to_cpp()
|
|
2161
|
+
else:
|
|
2162
|
+
elem_cpp = var_type.to_cpp()
|
|
2163
|
+
out.write(f'extern "C" {elem_cpp} {cpp_name}[];\n')
|
|
2164
|
+
elif stmt.linkage == VarLinkage.NATIVE_C:
|
|
2165
|
+
cpp_type = var_type.to_cpp()
|
|
2166
|
+
out.write(f'extern "C" {cpp_type} {cpp_name};\n')
|
|
2167
|
+
else:
|
|
2168
|
+
cpp_type = var_type.to_cpp()
|
|
2169
|
+
ns, bare = FunctionGenerator._split_native_name(cpp_name)
|
|
2170
|
+
if ns:
|
|
2171
|
+
out.write(f"namespace {ns} {{ extern {cpp_type} {bare}; }}\n")
|
|
2172
|
+
else:
|
|
2173
|
+
out.write(f"extern {cpp_type} {bare};\n")
|
|
2174
|
+
out.write("\n")
|
|
2175
|
+
|
|
2176
|
+
# Use nested namespace for dotted module names
|
|
2177
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
2178
|
+
out.write(f"namespace {ns} {{\n\n")
|
|
2179
|
+
|
|
2180
|
+
def generate_fwd_header(self, module: TpyModule, module_name: str) -> str:
|
|
2181
|
+
"""Emit `<mod>_fwd.hpp` -- forward declarations of every record /
|
|
2182
|
+
enum / @dynamic protocol defined in this module's namespace,
|
|
2183
|
+
plus the namespace skeleton itself. Cycle peers in the same SCC
|
|
2184
|
+
include this header in place of `<mod>.hpp` to break the
|
|
2185
|
+
cyclic complete-type include while keeping access to the type
|
|
2186
|
+
names.
|
|
2187
|
+
|
|
2188
|
+
Carries declarations only (no method bodies, no field
|
|
2189
|
+
layouts), so positions that need complete-type info (by-value
|
|
2190
|
+
fields, container elements, concrete inheritance, ...) are
|
|
2191
|
+
rejected up-front by `_check_workspace_completeness_cycles`.
|
|
2192
|
+
"""
|
|
2193
|
+
from .context import module_to_cpp_namespace
|
|
2194
|
+
ns = module_to_cpp_namespace(module_name)
|
|
2195
|
+
out_buf = io.StringIO()
|
|
2196
|
+
out_buf.write("// Generated by TurboPython Compiler -- forward declarations for cycle peers\n")
|
|
2197
|
+
out_buf.write("#pragma once\n\n")
|
|
2198
|
+
out_buf.write(f"namespace {ns} {{\n\n")
|
|
2199
|
+
for record in module.all_records():
|
|
2200
|
+
cpp_name = record.name.replace(".", "::")
|
|
2201
|
+
out_buf.write(f"struct {cpp_name};\n")
|
|
2202
|
+
for enum in module.all_enums():
|
|
2203
|
+
# @native enums: skip the forward decl. The user's
|
|
2204
|
+
# `# tpy: include(...)` provides the type; re-declaring with
|
|
2205
|
+
# the TPy-recorded underlying type would risk ODR mismatch.
|
|
2206
|
+
if enum.is_native:
|
|
2207
|
+
continue
|
|
2208
|
+
cpp_name = enum.name.replace(".", "::")
|
|
2209
|
+
# `enum class Name : underlying;` -- the underlying type
|
|
2210
|
+
# in the forward declaration MUST agree with the
|
|
2211
|
+
# definition's. Pull it from the EnumInfo attached to
|
|
2212
|
+
# the registered NominalType so both this fwd header
|
|
2213
|
+
# and the full header in `_gen_enum_decl` use the same
|
|
2214
|
+
# type and the C++ compiler accepts the redeclaration.
|
|
2215
|
+
enum_type = self.ctx.analyzer.registry.get_enum(enum.name)
|
|
2216
|
+
if enum_type is None:
|
|
2217
|
+
# Skeleton-pre-pop NominalType lacks an EnumInfo until
|
|
2218
|
+
# `register_enum` runs; default to `int32_t` (matches
|
|
2219
|
+
# `register_enum`'s default). The peer's full header
|
|
2220
|
+
# will redeclare with the same underlying type.
|
|
2221
|
+
underlying = "int32_t"
|
|
2222
|
+
else:
|
|
2223
|
+
underlying = enum_info_of(enum_type).underlying_type.to_cpp()
|
|
2224
|
+
out_buf.write(f"enum class {cpp_name} : {underlying};\n")
|
|
2225
|
+
for protocol in module.protocols:
|
|
2226
|
+
# @dynamic protocols emit a `struct {Name}` base class that
|
|
2227
|
+
# IS forward-declarable. Static (structural) protocols emit
|
|
2228
|
+
# only a C++20 concept, which is NOT forward-declarable;
|
|
2229
|
+
# cycle peers using a static protocol's name as a template
|
|
2230
|
+
# constraint must include the full peer header (a complete-
|
|
2231
|
+
# type position for the completeness-graph reject gate).
|
|
2232
|
+
if protocol.is_dynamic:
|
|
2233
|
+
ProtocolGenerator.emit_dynamic_base_forward_decl(out_buf, protocol)
|
|
2234
|
+
out_buf.write(f"\n}} // namespace {ns}\n")
|
|
2235
|
+
return out_buf.getvalue()
|
|
2236
|
+
|
|
2237
|
+
def _write_source_preamble(self, out: TextIO, module: TpyModule) -> None:
|
|
2238
|
+
out.write("// Generated by TurboPython Compiler\n")
|
|
2239
|
+
# Use full include path from include root (consistent with header includes)
|
|
2240
|
+
include_path = self._module_to_include_path(self.ctx.module_name)
|
|
2241
|
+
out.write(f'#include "{include_path}"\n')
|
|
2242
|
+
# Cycle peers: the .hpp included <peer>_fwd.hpp for each, but
|
|
2243
|
+
# the .cpp needs the FULL <peer>.hpp so function bodies can
|
|
2244
|
+
# see complete types of cycle peers' records.
|
|
2245
|
+
for peer in sorted(self.ctx.cycle_peers):
|
|
2246
|
+
if peer == self.ctx.module_name:
|
|
2247
|
+
continue
|
|
2248
|
+
out.write(f'#include "{self._module_to_include_path(peer)}"\n')
|
|
2249
|
+
out.write("\n")
|
|
2250
|
+
# EnumUtil definitions go before user namespace (they live in namespace tpy)
|
|
2251
|
+
if module.all_enums():
|
|
2252
|
+
self._gen_enum_source_defs(out, module)
|
|
2253
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
2254
|
+
out.write(f"namespace {ns} {{\n\n")
|
|
2255
|
+
|
|
2256
|
+
def _write_header_epilogue(self, out: TextIO) -> None:
|
|
2257
|
+
ns = module_to_cpp_namespace(self.ctx.module_name)
|
|
2258
|
+
out.write(f"}} // namespace {ns}\n")
|