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,907 @@
|
|
|
1
|
+
"""
|
|
2
|
+
TurboPython Protocol/Concept Code Generation
|
|
3
|
+
|
|
4
|
+
C++20 concept generation from TurboPython protocols and template header utilities.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from collections import namedtuple
|
|
9
|
+
from typing import TextIO, TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from ..typesys import (
|
|
12
|
+
TpyType, NominalType, TypeParamRef, TypeParamKind, ReadonlyType, VoidType, SelfType,
|
|
13
|
+
OptionalType, UnionType, OwnType, MethodSignature, is_protocol_type,
|
|
14
|
+
unwrap_readonly, unwrap_own, is_protocol_union, protocol_union_protocols,
|
|
15
|
+
protocol_union_has_none, unwrap_ref_type,
|
|
16
|
+
)
|
|
17
|
+
from ..parse import TpyProtocol, TpyRecord
|
|
18
|
+
from .context import INDENT, DUNDER_TO_BINARY_OP, CodeGenError, qualified_cpp_name
|
|
19
|
+
from ..type_def_registry import is_str_type, protocol_info_of, is_subtype
|
|
20
|
+
from ..symbol_binding import lookup_imported, SymbolKind
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from .context import CodeGenContext
|
|
24
|
+
|
|
25
|
+
# Unified protocol parameter info: name, list of protocol types (1+), nullable flag
|
|
26
|
+
ProtocolParamInfo = namedtuple('ProtocolParamInfo', ['name', 'protocols', 'has_none'])
|
|
27
|
+
|
|
28
|
+
# Adapter/RefAdapter template parameter name for the concrete impl type.
|
|
29
|
+
# `_GENERIC` is used inside the partial-spec template that's parameterized
|
|
30
|
+
# over the protocol's own type params; it uses the reserved `__tpy_` prefix
|
|
31
|
+
# to avoid colliding with any user-chosen identifier. `_NON_GENERIC` is the
|
|
32
|
+
# historical name kept for snapshot stability when the protocol has no type
|
|
33
|
+
# params.
|
|
34
|
+
_ADAPTER_IMPL_GENERIC = "__tpy_Impl"
|
|
35
|
+
_ADAPTER_IMPL_NON_GENERIC = "T"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ProtocolGenerator:
|
|
39
|
+
"""Generates C++20 concepts from TurboPython protocols."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, ctx: CodeGenContext):
|
|
42
|
+
self.ctx = ctx
|
|
43
|
+
|
|
44
|
+
def resolve_type_for_codegen(self, typ: TpyType) -> TpyType:
|
|
45
|
+
"""Resolve a type, setting is_protocol flag if it's actually a protocol.
|
|
46
|
+
|
|
47
|
+
During parsing, some types may be classified as records when they're
|
|
48
|
+
actually protocols (e.g., imported protocols). This method fixes that for codegen.
|
|
49
|
+
"""
|
|
50
|
+
if isinstance(typ, NominalType) and not typ.is_protocol:
|
|
51
|
+
# Check if this is actually a protocol. Probe with the protocol
|
|
52
|
+
# flag set so `protocol_info_of`'s qname-derived TypeDef lookup
|
|
53
|
+
# matches the authoritative `{module}.{name}` key.
|
|
54
|
+
probe = typ.with_protocol_flag(is_protocol=True)
|
|
55
|
+
if protocol_info_of(probe) is not None:
|
|
56
|
+
return probe
|
|
57
|
+
return typ
|
|
58
|
+
|
|
59
|
+
def is_static_protocol_param(self, typ: TpyType) -> bool:
|
|
60
|
+
"""Check if a type is a static protocol parameter (single or union, optional or not).
|
|
61
|
+
|
|
62
|
+
Returns True for:
|
|
63
|
+
- NominalType that is a static protocol
|
|
64
|
+
- OptionalType(static protocol)
|
|
65
|
+
- UnionType of 2+ static protocols (optionally with None)
|
|
66
|
+
Excludes @dynamic protocols.
|
|
67
|
+
"""
|
|
68
|
+
unwrapped = unwrap_own(unwrap_readonly(unwrap_ref_type(typ)))
|
|
69
|
+
# Optional[Protocol]
|
|
70
|
+
if isinstance(unwrapped, OptionalType):
|
|
71
|
+
resolved = self.resolve_type_for_codegen(unwrapped.inner)
|
|
72
|
+
if is_protocol_type(resolved):
|
|
73
|
+
protocol_info = protocol_info_of(resolved)
|
|
74
|
+
if protocol_info and protocol_info.is_dynamic:
|
|
75
|
+
return False
|
|
76
|
+
return True
|
|
77
|
+
return False
|
|
78
|
+
# Protocol union (2+ protocols, optionally with None)
|
|
79
|
+
if isinstance(unwrapped, UnionType):
|
|
80
|
+
resolved_members = tuple(self.resolve_type_for_codegen(m) for m in unwrapped.members)
|
|
81
|
+
resolved_union = UnionType(resolved_members)
|
|
82
|
+
return is_protocol_union(resolved_union)
|
|
83
|
+
# Single bare protocol
|
|
84
|
+
resolved = self.resolve_type_for_codegen(unwrapped)
|
|
85
|
+
if is_protocol_type(resolved):
|
|
86
|
+
protocol_info = protocol_info_of(resolved)
|
|
87
|
+
if protocol_info and protocol_info.is_dynamic:
|
|
88
|
+
return False
|
|
89
|
+
return True
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
def get_all_protocol_params(
|
|
93
|
+
self, params: list[tuple[str, TpyType]]
|
|
94
|
+
) -> list[ProtocolParamInfo]:
|
|
95
|
+
"""Get unified list of all static protocol parameters.
|
|
96
|
+
|
|
97
|
+
Handles single protocols (bare or Optional) and protocol unions.
|
|
98
|
+
Returns ProtocolParamInfo(name, protocols, has_none) for each.
|
|
99
|
+
"""
|
|
100
|
+
result: list[ProtocolParamInfo] = []
|
|
101
|
+
for pname, ptype in params:
|
|
102
|
+
unwrapped = unwrap_own(unwrap_readonly(unwrap_ref_type(ptype)))
|
|
103
|
+
|
|
104
|
+
# Optional[Protocol] -- single protocol, nullable
|
|
105
|
+
if isinstance(unwrapped, OptionalType):
|
|
106
|
+
resolved = self.resolve_type_for_codegen(unwrapped.inner)
|
|
107
|
+
if is_protocol_type(resolved) and isinstance(resolved, NominalType):
|
|
108
|
+
protocol_info = protocol_info_of(resolved)
|
|
109
|
+
if protocol_info and protocol_info.is_dynamic:
|
|
110
|
+
continue
|
|
111
|
+
result.append(ProtocolParamInfo(pname, [resolved], True))
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# Protocol union (2+ protocols, optionally with None)
|
|
115
|
+
if isinstance(unwrapped, UnionType):
|
|
116
|
+
resolved_members = tuple(self.resolve_type_for_codegen(m) for m in unwrapped.members)
|
|
117
|
+
resolved_union = UnionType(resolved_members)
|
|
118
|
+
if is_protocol_union(resolved_union):
|
|
119
|
+
protos = [m for m in protocol_union_protocols(resolved_union) if isinstance(m, NominalType)]
|
|
120
|
+
has_none = protocol_union_has_none(resolved_union)
|
|
121
|
+
result.append(ProtocolParamInfo(pname, protos, has_none))
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Single bare protocol
|
|
125
|
+
resolved = self.resolve_type_for_codegen(unwrapped)
|
|
126
|
+
if is_protocol_type(resolved) and isinstance(resolved, NominalType):
|
|
127
|
+
protocol_info = protocol_info_of(resolved)
|
|
128
|
+
if protocol_info and protocol_info.is_dynamic:
|
|
129
|
+
continue
|
|
130
|
+
result.append(ProtocolParamInfo(pname, [resolved], False))
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
def get_concept_name(self, protocol: NominalType) -> str:
|
|
135
|
+
"""Get the C++ concept name for a protocol type.
|
|
136
|
+
|
|
137
|
+
For @dynamic protocols the concept is renamed to __{Name}_Concept__
|
|
138
|
+
so the clean name is free for the base class struct.
|
|
139
|
+
Non-dynamic protocols keep their original name.
|
|
140
|
+
"""
|
|
141
|
+
# Unified lookup via registry
|
|
142
|
+
protocol_info = protocol_info_of(protocol)
|
|
143
|
+
if protocol_info and protocol_info.cpp_concept:
|
|
144
|
+
return protocol_info.cpp_concept
|
|
145
|
+
is_dynamic = protocol_info is not None and protocol_info.is_dynamic
|
|
146
|
+
# Check if this is an imported protocol from another user module
|
|
147
|
+
qual = lookup_imported(
|
|
148
|
+
self.ctx.analyzer.ctx.module_attributes, protocol.name,
|
|
149
|
+
SymbolKind.PROTOCOL_STATIC, SymbolKind.PROTOCOL_DYNAMIC)
|
|
150
|
+
if qual is not None:
|
|
151
|
+
source_module, original_name = qual
|
|
152
|
+
cpp_name = f"__{original_name}_Concept__" if is_dynamic else original_name
|
|
153
|
+
return qualified_cpp_name(source_module, cpp_name)
|
|
154
|
+
# Protocol from a bare-imported module (import typing + typing.Sized)
|
|
155
|
+
if protocol_info and protocol_info.module and protocol_info.module in self.ctx.user_module_imports:
|
|
156
|
+
cpp_name = f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
|
|
157
|
+
return qualified_cpp_name(protocol_info.module, cpp_name)
|
|
158
|
+
# Protocol from an implicit stdlib module (typing, tpy) -- needs qualified name
|
|
159
|
+
if protocol_info and protocol_info.module and protocol_info.module in self.ctx.all_user_modules:
|
|
160
|
+
cpp_name = f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
|
|
161
|
+
return qualified_cpp_name(protocol_info.module, cpp_name)
|
|
162
|
+
# Protocol defined in the current module (or unknown origin fallback)
|
|
163
|
+
return f"__{protocol.name}_Concept__" if is_dynamic else protocol.name
|
|
164
|
+
|
|
165
|
+
def get_dynamic_base_name(self, protocol: NominalType) -> str:
|
|
166
|
+
"""Get the (possibly qualified) C++ base class name for a @dynamic protocol.
|
|
167
|
+
|
|
168
|
+
Type args render through `t.to_cpp()`; for unresolved TypeParamRef
|
|
169
|
+
args (inside a nested template emission) this is the bare name `T`,
|
|
170
|
+
so the same helper covers both call-site instantiations
|
|
171
|
+
(`Awaitable<int32_t>`) and nested-template references (`Awaitable<T>`).
|
|
172
|
+
|
|
173
|
+
For protocols that are both @dynamic and @native (cpp_concept set),
|
|
174
|
+
the C++ class name comes from the @native annotation -- the abstract
|
|
175
|
+
base lives in a runtime header rather than codegen output. Used by
|
|
176
|
+
Throwable (`@native("tpy::Throwable") @dynamic`) to bridge to
|
|
177
|
+
`runtime/cpp/include/tpy/throwable.hpp::tpy::Throwable`.
|
|
178
|
+
"""
|
|
179
|
+
protocol_info = protocol_info_of(protocol)
|
|
180
|
+
if protocol_info and protocol_info.cpp_concept:
|
|
181
|
+
base = protocol_info.cpp_concept
|
|
182
|
+
if protocol.type_args:
|
|
183
|
+
args_cpp = ", ".join(t.to_cpp() for t in protocol.type_args)
|
|
184
|
+
return f"{base}<{args_cpp}>"
|
|
185
|
+
return base
|
|
186
|
+
name = protocol.name
|
|
187
|
+
qual = lookup_imported(
|
|
188
|
+
self.ctx.analyzer.ctx.module_attributes, name,
|
|
189
|
+
SymbolKind.PROTOCOL_STATIC, SymbolKind.PROTOCOL_DYNAMIC)
|
|
190
|
+
if qual is not None:
|
|
191
|
+
source_module, original_name = qual
|
|
192
|
+
base = qualified_cpp_name(source_module, original_name)
|
|
193
|
+
else:
|
|
194
|
+
base = self._qualify_protocol_via_qname(protocol) or name
|
|
195
|
+
if protocol.type_args:
|
|
196
|
+
args_cpp = ", ".join(t.to_cpp() for t in protocol.type_args)
|
|
197
|
+
return f"{base}<{args_cpp}>"
|
|
198
|
+
return base
|
|
199
|
+
|
|
200
|
+
def _qualify_protocol_via_qname(self, protocol: NominalType) -> str | None:
|
|
201
|
+
"""Fallback qualification for protocols not bound in the current
|
|
202
|
+
module's attributes -- e.g. implicit-stdlib protocols referenced
|
|
203
|
+
only through a sema-synthesized type like `Cancellable[T]`
|
|
204
|
+
constructed by `make_cancellable` for async-def returns. Skips
|
|
205
|
+
when the protocol lives in the current module (the bare local
|
|
206
|
+
name is correct there)."""
|
|
207
|
+
qname = protocol._module_qname
|
|
208
|
+
if not qname or "." not in qname:
|
|
209
|
+
return None
|
|
210
|
+
source_module, original_name = qname.rsplit(".", 1)
|
|
211
|
+
if source_module == self.ctx.analyzer.ctx.module_name:
|
|
212
|
+
return None
|
|
213
|
+
return qualified_cpp_name(source_module, original_name)
|
|
214
|
+
|
|
215
|
+
def get_dynamic_adapter_type(self, protocol: NominalType, concrete_cpp: str) -> str:
|
|
216
|
+
"""Get the full C++ type for an owning adapter: ::tpy::Adapter<Base, Concrete>."""
|
|
217
|
+
base = self.get_dynamic_base_name(protocol)
|
|
218
|
+
return f"::tpy::Adapter<{base}, {concrete_cpp}>"
|
|
219
|
+
|
|
220
|
+
def get_dynamic_ref_adapter_type(self, protocol: NominalType, concrete_cpp: str) -> str:
|
|
221
|
+
"""Get the full C++ type for a ref adapter: ::tpy::RefAdapter<Base, Concrete>."""
|
|
222
|
+
base = self.get_dynamic_base_name(protocol)
|
|
223
|
+
return f"::tpy::RefAdapter<{base}, {concrete_cpp}>"
|
|
224
|
+
|
|
225
|
+
def dynamic_narrow_cast_rhs(
|
|
226
|
+
self, cpp_type: str, check_type: TpyType, source_inner: 'TpyType | None',
|
|
227
|
+
cast_arg: str, *, is_const: bool,
|
|
228
|
+
) -> str:
|
|
229
|
+
"""Build the pointer-producing RHS that narrows `cast_arg` (a
|
|
230
|
+
`[const] P*` payload pointer) to a `[const] Sub*` for isinstance
|
|
231
|
+
dispatch. The result is used as the if-init / extraction-local
|
|
232
|
+
initializer and, against nullptr, as the branch condition.
|
|
233
|
+
|
|
234
|
+
An inheritance conformer (or a polymorphic-class root, which has no
|
|
235
|
+
structural conformers) uses `dynamic_cast`. A STRUCTURAL conformer of
|
|
236
|
+
a @dynamic protocol routes through `tpy::dyn_adapter_cast`, which
|
|
237
|
+
unwraps the `Adapter`/`RefAdapter` the structural value sits inside --
|
|
238
|
+
a plain `dynamic_cast<Sub*>` would always fail there. The structural
|
|
239
|
+
overload infers const-ness from `cast_arg`, so `is_const` only shapes
|
|
240
|
+
the `dynamic_cast` form."""
|
|
241
|
+
if (is_protocol_type(source_inner)
|
|
242
|
+
and isinstance(check_type, NominalType)
|
|
243
|
+
and not self.directly_implements_dynamic(check_type, source_inner)):
|
|
244
|
+
base = self.get_dynamic_base_name(source_inner)
|
|
245
|
+
return f"::tpy::dyn_adapter_cast<{base}, {cpp_type}>({cast_arg})"
|
|
246
|
+
const_pfx = "const " if is_const else ""
|
|
247
|
+
return f"dynamic_cast<{const_pfx}{cpp_type}*>({cast_arg})"
|
|
248
|
+
|
|
249
|
+
def dyn_protocol_forward_ok(self, source: TpyType, target: TpyType) -> bool:
|
|
250
|
+
"""True if `source` (@dynamic protocol) can be forwarded as `target`
|
|
251
|
+
(@dynamic protocol) without an Adapter wrap -- same protocol (joint
|
|
252
|
+
qualified_name + type_args check, so generic instantiations like
|
|
253
|
+
`Container[int]` and `Container[str]` stay distinct) or inheriting peer.
|
|
254
|
+
"""
|
|
255
|
+
if not (isinstance(source, NominalType) and isinstance(target, NominalType)):
|
|
256
|
+
return False
|
|
257
|
+
if (source.qualified_name() == target.qualified_name()
|
|
258
|
+
and source.type_args == target.type_args):
|
|
259
|
+
return True
|
|
260
|
+
return is_subtype(
|
|
261
|
+
self.ctx.analyzer.registry.scan_by_short_name(source.name), target.name
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
def directly_implements_dynamic(self, concrete_type: TpyType, protocol: NominalType) -> bool:
|
|
265
|
+
"""Check if concrete_type inherits a @dynamic protocol (directly or transitively).
|
|
266
|
+
|
|
267
|
+
Returns True when the record explicitly implements a @dynamic protocol that
|
|
268
|
+
is (or transitively inherits from) `protocol`. This means the C++ struct
|
|
269
|
+
inherits the protocol base class through the inheritance chain and no adapter
|
|
270
|
+
wrapping is needed.
|
|
271
|
+
|
|
272
|
+
Match is by short name; type_args are ignored. Cross-instantiation
|
|
273
|
+
mismatches (e.g. assigning `Container[Int32]` into `Container[str]`)
|
|
274
|
+
are already rejected by sema before this helper runs.
|
|
275
|
+
|
|
276
|
+
The ``is_dynamic`` filter is applied at the IMPLEMENTED-protocol level
|
|
277
|
+
(chain root), not at the target. A non-@dynamic protocol contributes no
|
|
278
|
+
C++ base, so a chain rooted in a non-@dynamic implemented protocol
|
|
279
|
+
cannot make ``concrete_type`` C++-inherit anything -- even if a
|
|
280
|
+
@dynamic ancestor sits further up the chain. This differs from
|
|
281
|
+
``sema/type_ops._inherits_protocol`` (which filters on the target,
|
|
282
|
+
not the root); the closure cached on ``RecordInfo.transitive_supertypes``
|
|
283
|
+
encodes the unfiltered closure used by the latter, so this helper keeps
|
|
284
|
+
an explicit per-element walk and uses ``is_subtype`` only for the
|
|
285
|
+
per-protocol ancestor step.
|
|
286
|
+
|
|
287
|
+
@native records are excluded: their C++ representation is opaque to
|
|
288
|
+
codegen (the struct is hand-written elsewhere), so even when the TPy
|
|
289
|
+
declaration claims `class NativeRec(SomeDynProto)`, the C++ struct
|
|
290
|
+
almost certainly does NOT inherit the codegen-emitted protocol base.
|
|
291
|
+
Routing through Adapter is the only safe lowering for those types.
|
|
292
|
+
Example: `BaseException` inherits `Throwable` at the TPy level for
|
|
293
|
+
polymorphism dispatch on `Optional[BaseException]`, but
|
|
294
|
+
`::tpy::BaseException` in core.hpp does not inherit `tpystd::tpy::Throwable`.
|
|
295
|
+
"""
|
|
296
|
+
if not isinstance(concrete_type, NominalType) or not concrete_type.is_user_record:
|
|
297
|
+
return False
|
|
298
|
+
record_info = self.ctx.analyzer.registry.get_record(concrete_type.name)
|
|
299
|
+
if record_info is None or record_info.is_native:
|
|
300
|
+
return False
|
|
301
|
+
proto_name = protocol.name
|
|
302
|
+
for p in record_info.implemented_protocols:
|
|
303
|
+
pi = protocol_info_of(p)
|
|
304
|
+
if pi is None or not pi.is_dynamic:
|
|
305
|
+
continue
|
|
306
|
+
if p.name == proto_name or is_subtype(pi, proto_name):
|
|
307
|
+
return True
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
def gen_record_template_parts(
|
|
311
|
+
self,
|
|
312
|
+
type_params: list[str],
|
|
313
|
+
type_param_bounds: dict[str, NominalType],
|
|
314
|
+
type_param_kinds: list[TypeParamKind] | None = None
|
|
315
|
+
) -> list[str]:
|
|
316
|
+
"""Render the per-param parts of a generic record's template header.
|
|
317
|
+
|
|
318
|
+
For unbounded TYPE params: `typename T`
|
|
319
|
+
For protocol-bounded TYPE params: `Comparable T` / `Sequence<int32_t> T`
|
|
320
|
+
For INT params: `std::size_t N`
|
|
321
|
+
For class / type-param bounds (not C++ concepts): `typename T` -- the
|
|
322
|
+
TPy-side subtype bound is enforced at the call site (satisfies_bound).
|
|
323
|
+
"""
|
|
324
|
+
template_parts = []
|
|
325
|
+
for i, tp in enumerate(type_params):
|
|
326
|
+
kind = type_param_kinds[i] if type_param_kinds and i < len(type_param_kinds) else TypeParamKind.TYPE
|
|
327
|
+
if kind == TypeParamKind.INT:
|
|
328
|
+
template_parts.append(f"std::size_t {tp}")
|
|
329
|
+
elif tp in type_param_bounds and is_protocol_type(type_param_bounds[tp]):
|
|
330
|
+
bound = type_param_bounds[tp]
|
|
331
|
+
concept_name = self.get_concept_name(bound)
|
|
332
|
+
if bound.type_args:
|
|
333
|
+
type_args_cpp = ", ".join(t.to_cpp() for t in bound.type_args)
|
|
334
|
+
template_parts.append(f"{concept_name}<{type_args_cpp}> {tp}")
|
|
335
|
+
else:
|
|
336
|
+
template_parts.append(f"{concept_name} {tp}")
|
|
337
|
+
else:
|
|
338
|
+
template_parts.append(f"typename {tp}")
|
|
339
|
+
return template_parts
|
|
340
|
+
|
|
341
|
+
def gen_record_template_header(
|
|
342
|
+
self,
|
|
343
|
+
type_params: list[str],
|
|
344
|
+
type_param_bounds: dict[str, NominalType],
|
|
345
|
+
type_param_kinds: list[TypeParamKind] | None = None
|
|
346
|
+
) -> str:
|
|
347
|
+
parts = self.gen_record_template_parts(type_params, type_param_bounds, type_param_kinds)
|
|
348
|
+
return f"template<{', '.join(parts)}>"
|
|
349
|
+
|
|
350
|
+
def _concept_constraint(self, pname: str, ptype: NominalType) -> str:
|
|
351
|
+
"""Build the concept constraint expression for a protocol template param.
|
|
352
|
+
|
|
353
|
+
Returns e.g. '::tpy::Sized<T_items>' or '::tpy::Sequence<T_items, int32_t>'.
|
|
354
|
+
"""
|
|
355
|
+
concept_name = self.get_concept_name(ptype)
|
|
356
|
+
if ptype.type_args:
|
|
357
|
+
type_args_cpp = ", ".join(t.to_cpp() for t in ptype.type_args)
|
|
358
|
+
return f"{concept_name}<T_{pname}, {type_args_cpp}>"
|
|
359
|
+
return f"{concept_name}<T_{pname}>"
|
|
360
|
+
|
|
361
|
+
def gen_combined_template_header(
|
|
362
|
+
self,
|
|
363
|
+
type_params: list[str],
|
|
364
|
+
protocol_params: list[ProtocolParamInfo],
|
|
365
|
+
type_param_bounds: dict[str, NominalType] | None = None,
|
|
366
|
+
*, emit_defaults: bool = True,
|
|
367
|
+
) -> str:
|
|
368
|
+
"""Generate template header combining type parameters and concept constraints.
|
|
369
|
+
|
|
370
|
+
For generic functions: template<typename T>
|
|
371
|
+
For generic functions with protocols: template<typename T, ::tpy::Sized T_items>
|
|
372
|
+
For bounded type params: template<Comparable T>
|
|
373
|
+
For protocol unions: template<typename T_items> requires (A<T_items> || B<T_items>)
|
|
374
|
+
"""
|
|
375
|
+
template_parts = []
|
|
376
|
+
|
|
377
|
+
# Add type parameters for generic functions (with optional bounds)
|
|
378
|
+
for tp in type_params:
|
|
379
|
+
if (type_param_bounds and tp in type_param_bounds
|
|
380
|
+
and is_protocol_type(type_param_bounds[tp])):
|
|
381
|
+
bound = type_param_bounds[tp]
|
|
382
|
+
concept_name = self.get_concept_name(bound)
|
|
383
|
+
if bound.type_args:
|
|
384
|
+
type_args_cpp = ", ".join(t.to_cpp() for t in bound.type_args)
|
|
385
|
+
template_parts.append(f"{concept_name}<{type_args_cpp}> {tp}")
|
|
386
|
+
else:
|
|
387
|
+
template_parts.append(f"{concept_name} {tp}")
|
|
388
|
+
else:
|
|
389
|
+
# A class or type-param bound (`U: Animal` / `U: T`) is not a
|
|
390
|
+
# C++ concept; emit an unconstrained param. The subtype bound
|
|
391
|
+
# is enforced at the TPy call site (satisfies_bound).
|
|
392
|
+
template_parts.append(f"typename {tp}")
|
|
393
|
+
|
|
394
|
+
# Add protocol params with concept constraints
|
|
395
|
+
requires_parts = []
|
|
396
|
+
for info in protocol_params:
|
|
397
|
+
pname = info.name
|
|
398
|
+
# Single required protocol: clean `Concept T_x` syntax
|
|
399
|
+
if len(info.protocols) == 1 and not info.has_none:
|
|
400
|
+
ptype = info.protocols[0]
|
|
401
|
+
concept_name = self.get_concept_name(ptype)
|
|
402
|
+
if ptype.type_args:
|
|
403
|
+
type_args_cpp = ", ".join(t.to_cpp() for t in ptype.type_args)
|
|
404
|
+
template_parts.append(f"{concept_name}<{type_args_cpp}> T_{pname}")
|
|
405
|
+
else:
|
|
406
|
+
template_parts.append(f"{concept_name} T_{pname}")
|
|
407
|
+
else:
|
|
408
|
+
# Multiple protocols or nullable: typename + requires clause.
|
|
409
|
+
# `nullptr_t` here is the *nullable-static-protocol-param*
|
|
410
|
+
# sentinel (e.g. `def f(p: Sized | None)` -- the param accepts
|
|
411
|
+
# nullptr at the C++ level so `f(None)` lowers to a call with
|
|
412
|
+
# no arg). This is distinct from `None` as a value-bearing
|
|
413
|
+
# generic type argument (which lowers to std::monostate via
|
|
414
|
+
# NoneType.to_cpp): the protocol-template default is matched
|
|
415
|
+
# by passing literal `nullptr` and gated by `if constexpr`
|
|
416
|
+
# bodies, never stored as a value.
|
|
417
|
+
default_part = " = std::nullptr_t" if info.has_none and emit_defaults else ""
|
|
418
|
+
template_parts.append(f"typename T_{pname}{default_part}")
|
|
419
|
+
constraints = []
|
|
420
|
+
if info.has_none:
|
|
421
|
+
constraints.append(f"std::same_as<T_{pname}, std::nullptr_t>")
|
|
422
|
+
for proto in info.protocols:
|
|
423
|
+
constraints.append(self._concept_constraint(pname, proto))
|
|
424
|
+
requires_parts.append(f"({' || '.join(constraints)})")
|
|
425
|
+
|
|
426
|
+
if not template_parts:
|
|
427
|
+
return ""
|
|
428
|
+
result = f"template<{', '.join(template_parts)}>\n"
|
|
429
|
+
if requires_parts:
|
|
430
|
+
result += f" requires {' && '.join(requires_parts)}\n"
|
|
431
|
+
return result
|
|
432
|
+
|
|
433
|
+
def collect_concept_methods(self, protocol_name: str) -> list[MethodSignature]:
|
|
434
|
+
return self.ctx.analyzer.protocols.collect_protocol_methods(protocol_name)
|
|
435
|
+
|
|
436
|
+
def is_protocol_const(self, protocol_name: str) -> bool:
|
|
437
|
+
"""Check if a protocol is functionally const (all methods are readonly).
|
|
438
|
+
|
|
439
|
+
True when the protocol-level is_readonly flag is set, or when every
|
|
440
|
+
method (including inherited ones) is individually @readonly.
|
|
441
|
+
"""
|
|
442
|
+
pi = self.ctx.analyzer.registry.scan_by_short_name(protocol_name)
|
|
443
|
+
if pi is None:
|
|
444
|
+
return False
|
|
445
|
+
if pi.is_readonly:
|
|
446
|
+
return True
|
|
447
|
+
all_methods = self.collect_concept_methods(protocol_name)
|
|
448
|
+
return bool(all_methods) and all(m.is_readonly for m in all_methods)
|
|
449
|
+
|
|
450
|
+
def collect_concept_fields(self, protocol_name: str) -> list[tuple[str, TpyType]]:
|
|
451
|
+
return self.ctx.analyzer.protocols.collect_protocol_fields(protocol_name)
|
|
452
|
+
|
|
453
|
+
def gen_concept_decl(self, out: TextIO, protocol: TpyProtocol) -> None:
|
|
454
|
+
"""Generate a C++20 concept for a user-defined protocol.
|
|
455
|
+
|
|
456
|
+
SelfType in method signatures is rendered as 'T' (the template parameter).
|
|
457
|
+
This allows the concept to check that e.g., T + T -> T.
|
|
458
|
+
|
|
459
|
+
For generic protocols like Container[T], we generate:
|
|
460
|
+
- template<typename T, typename _T0> where T is the checked type and _T0 is the protocol's T
|
|
461
|
+
- Usage: Container<int32_t> V means V must satisfy Container<V, int32_t>
|
|
462
|
+
|
|
463
|
+
For protocol inheritance, includes requirements from all parent protocols.
|
|
464
|
+
|
|
465
|
+
Protocols with cpp_concept (e.g. @native marker protocols) are backed by
|
|
466
|
+
runtime C++ concepts and don't need generated concept definitions.
|
|
467
|
+
|
|
468
|
+
Returns True if a concept was emitted, False if skipped.
|
|
469
|
+
"""
|
|
470
|
+
# Skip protocols backed by runtime C++ concepts
|
|
471
|
+
protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
|
|
472
|
+
if protocol_info and protocol_info.cpp_concept:
|
|
473
|
+
return False
|
|
474
|
+
# For @dynamic protocols, forward-declare the abstract base struct so
|
|
475
|
+
# the concept can mention the protocol's own type in method signatures
|
|
476
|
+
# (e.g. `clone(self) -> Own[Self]` lowering to `std::unique_ptr<Proto>`
|
|
477
|
+
# inside a convertible_to constraint). The base struct is fully defined
|
|
478
|
+
# by gen_dynamic_base_class below; concept constraints only require
|
|
479
|
+
# the type to be complete at instantiation time.
|
|
480
|
+
if protocol.is_dynamic:
|
|
481
|
+
self.emit_dynamic_base_forward_decl(out, protocol)
|
|
482
|
+
self.ctx.emit_preceding_comments(out, protocol.loc)
|
|
483
|
+
self.ctx.emit_source_comment(out, protocol.loc)
|
|
484
|
+
# Build template params: T (checked type) + one for each protocol type param
|
|
485
|
+
template_params = ["typename T"]
|
|
486
|
+
type_param_map: dict[str, str] = {} # Protocol type param -> C++ template param
|
|
487
|
+
for i, tp in enumerate(protocol.type_params):
|
|
488
|
+
cpp_param = f"_T{i}"
|
|
489
|
+
template_params.append(f"typename {cpp_param}")
|
|
490
|
+
type_param_map[tp] = cpp_param
|
|
491
|
+
|
|
492
|
+
out.write(f"template<{', '.join(template_params)}>\n")
|
|
493
|
+
|
|
494
|
+
# Collect all methods including inherited ones
|
|
495
|
+
all_methods = self.collect_concept_methods(protocol.name)
|
|
496
|
+
all_fields_for_decl = self.collect_concept_fields(protocol.name)
|
|
497
|
+
|
|
498
|
+
# @dynamic protocols use __{Name}_Concept__ so the clean name is
|
|
499
|
+
# free for the base class struct
|
|
500
|
+
concept_name = (f"__{protocol.name}_Concept__"
|
|
501
|
+
if protocol.is_dynamic else protocol.name)
|
|
502
|
+
# Marker (no methods, no fields) -> trivially-satisfied concept.
|
|
503
|
+
# An empty `requires(T& t) { }` body is ill-formed in C++20 (GCC/Clang
|
|
504
|
+
# reject it), so emit `= true` instead. Used by markerless @dynamic
|
|
505
|
+
# protocols that act as phylum tags (e.g. Throwable for the exception
|
|
506
|
+
# tree's polymorphism predicate).
|
|
507
|
+
if not all_methods and not all_fields_for_decl:
|
|
508
|
+
out.write(f"concept {concept_name} = true;\n")
|
|
509
|
+
return True
|
|
510
|
+
if self.is_protocol_const(protocol.name):
|
|
511
|
+
out.write(f"concept {concept_name} = requires(const T& t) {{\n")
|
|
512
|
+
else:
|
|
513
|
+
out.write(f"concept {concept_name} = requires(T& t) {{\n")
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def subst_type(typ: TpyType) -> TpyType:
|
|
517
|
+
"""Substitute protocol type params recursively in a type."""
|
|
518
|
+
if isinstance(typ, TypeParamRef) and typ.name in type_param_map:
|
|
519
|
+
# Return a TypeParamRef with the mapped name
|
|
520
|
+
return TypeParamRef(type_param_map[typ.name])
|
|
521
|
+
return typ.map_inner_types(subst_type)
|
|
522
|
+
|
|
523
|
+
def subst_to_cpp(typ: TpyType) -> str:
|
|
524
|
+
"""Convert type to C++, substituting protocol type params."""
|
|
525
|
+
return subst_type(typ).to_cpp()
|
|
526
|
+
|
|
527
|
+
def concept_type_cpp(typ: TpyType) -> str:
|
|
528
|
+
"""C++ type for concept constraint, with covariant str handling.
|
|
529
|
+
|
|
530
|
+
Protocol -> str uses string_view so implementations can return
|
|
531
|
+
any string type (str, StrView, String).
|
|
532
|
+
"""
|
|
533
|
+
resolved = subst_type(typ)
|
|
534
|
+
if is_str_type(resolved):
|
|
535
|
+
return "std::string_view"
|
|
536
|
+
return resolved.to_cpp()
|
|
537
|
+
|
|
538
|
+
for method_sig in all_methods:
|
|
539
|
+
# Generate requirement for each method.
|
|
540
|
+
# SelfType.to_cpp() returns "T", so this handles Self -> T substitution.
|
|
541
|
+
# Protocol type params (e.g., T in Container[T]) are mapped to _T0, _T1, etc.
|
|
542
|
+
ret_cpp = concept_type_cpp(method_sig.return_type)
|
|
543
|
+
# Skip return type check when the C++ return doesn't match the
|
|
544
|
+
# protocol signature: protocol returns (Iterator[T] -- can't
|
|
545
|
+
# convertible_to a concept), __next__ (C++ returns
|
|
546
|
+
# std::expected<T, StopIteration>, not T), and Self on __iter__
|
|
547
|
+
# (just checks the expression is valid, not the return type).
|
|
548
|
+
skip_return_check = (is_protocol_type(method_sig.return_type)
|
|
549
|
+
or method_sig.name == "__next__"
|
|
550
|
+
or (isinstance(method_sig.return_type, SelfType)
|
|
551
|
+
and method_sig.name not in DUNDER_TO_BINARY_OP))
|
|
552
|
+
|
|
553
|
+
# Build the C++ call expression from DUNDER_CPP_TEMPLATES, binary
|
|
554
|
+
# operator map, or direct member call (in that priority order).
|
|
555
|
+
from ..modules import get_dunder_cpp_template
|
|
556
|
+
cpp_tmpl = get_dunder_cpp_template(method_sig.name)
|
|
557
|
+
if cpp_tmpl is not None:
|
|
558
|
+
# Expand template: {self} -> t, {0}/{1}/... -> std::declval<ParamCpp>()
|
|
559
|
+
call_expr = cpp_tmpl.replace("{self}", "t")
|
|
560
|
+
for i, (_, ptype) in enumerate(method_sig.params):
|
|
561
|
+
call_expr = call_expr.replace(f"{{{i}}}", f"std::declval<{subst_to_cpp(ptype)}>()")
|
|
562
|
+
elif method_sig.name in DUNDER_TO_BINARY_OP and len(method_sig.params) == 1:
|
|
563
|
+
cpp_op = DUNDER_TO_BINARY_OP[method_sig.name]
|
|
564
|
+
_, ptype = method_sig.params[0]
|
|
565
|
+
call_expr = f"t {cpp_op} std::declval<{subst_to_cpp(ptype)}>()"
|
|
566
|
+
else:
|
|
567
|
+
param_exprs = [f"std::declval<{subst_to_cpp(ptype)}>()" for _, ptype in method_sig.params]
|
|
568
|
+
call_expr = f"t.{method_sig.name}({', '.join(param_exprs)})"
|
|
569
|
+
|
|
570
|
+
if skip_return_check:
|
|
571
|
+
out.write(f"{INDENT}{call_expr};\n")
|
|
572
|
+
else:
|
|
573
|
+
out.write(f"{INDENT}{{ {call_expr} }} -> std::convertible_to<{ret_cpp}>;\n")
|
|
574
|
+
|
|
575
|
+
# Collect all fields including inherited ones
|
|
576
|
+
all_fields = self.collect_concept_fields(protocol.name)
|
|
577
|
+
for field_name, field_type in all_fields:
|
|
578
|
+
field_cpp = concept_type_cpp(field_type)
|
|
579
|
+
out.write(f"{INDENT}{{ t.{field_name} }} -> std::convertible_to<{field_cpp}>;\n")
|
|
580
|
+
|
|
581
|
+
out.write("};\n")
|
|
582
|
+
return True
|
|
583
|
+
|
|
584
|
+
def gen_dynamic_base_class(self, out: TextIO, protocol: TpyProtocol) -> None:
|
|
585
|
+
"""Generate abstract base class for a @dynamic protocol.
|
|
586
|
+
|
|
587
|
+
Emitted inside the user namespace. The base class gets the protocol's
|
|
588
|
+
clean name (e.g., struct Pet) so C++ interop code can use it directly.
|
|
589
|
+
When this protocol extends other @dynamic protocols, the base class
|
|
590
|
+
inherits from their bases (e.g., NamedPet : Pet).
|
|
591
|
+
|
|
592
|
+
For generic @dynamic protocols (e.g. Awaitable[T]), the base is a
|
|
593
|
+
class template parameterized on the protocol's type params; method
|
|
594
|
+
signatures referring to those params render via TypeParamRef.to_cpp()
|
|
595
|
+
which already returns the bare name ("T").
|
|
596
|
+
|
|
597
|
+
Note on naming: the *concept* (emitted by gen_concept_decl) renames
|
|
598
|
+
protocol type params to `_T0`, `_T1`, ... because its first slot is
|
|
599
|
+
already taken by the checked type `T`. The base class here uses the
|
|
600
|
+
bare names directly. The two schemes round-trip via the C++20
|
|
601
|
+
abbreviated constraint form -- see gen_dynamic_adapter_specs for the
|
|
602
|
+
construction that bridges them.
|
|
603
|
+
"""
|
|
604
|
+
protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
|
|
605
|
+
if protocol_info is None:
|
|
606
|
+
return
|
|
607
|
+
|
|
608
|
+
all_methods = self.collect_concept_methods(protocol.name)
|
|
609
|
+
|
|
610
|
+
# Collect @dynamic parent bases and their already-declared methods
|
|
611
|
+
dynamic_parent_bases: list[str] = []
|
|
612
|
+
parent_dynamic_methods: set[str] = set()
|
|
613
|
+
for parent in protocol_info.parent_protocols:
|
|
614
|
+
parent_info = self.ctx.analyzer.registry.scan_by_short_name(parent.name)
|
|
615
|
+
if parent_info and parent_info.is_dynamic:
|
|
616
|
+
dynamic_parent_bases.append(self.get_dynamic_base_name(parent))
|
|
617
|
+
for m in self.collect_concept_methods(parent.name):
|
|
618
|
+
parent_dynamic_methods.add(m.name)
|
|
619
|
+
|
|
620
|
+
# Methods to declare in this base class (exclude those in @dynamic parents)
|
|
621
|
+
base_methods = [m for m in all_methods if m.name not in parent_dynamic_methods]
|
|
622
|
+
|
|
623
|
+
if protocol.type_params:
|
|
624
|
+
out.write(self.gen_record_template_header(protocol.type_params, {}) + "\n")
|
|
625
|
+
base_name = protocol.name
|
|
626
|
+
if dynamic_parent_bases:
|
|
627
|
+
bases_str = ", ".join(dynamic_parent_bases)
|
|
628
|
+
out.write(f"struct {base_name} : {bases_str} {{\n")
|
|
629
|
+
else:
|
|
630
|
+
out.write(f"struct {base_name} {{\n")
|
|
631
|
+
for method_sig in base_methods:
|
|
632
|
+
ret_cpp = self._dynamic_return_type(method_sig)
|
|
633
|
+
const_qual = " const" if self._is_readonly_method(method_sig, protocol_info) else ""
|
|
634
|
+
params_cpp = self._dynamic_param_list(method_sig)
|
|
635
|
+
out.write(f"{INDENT}virtual {ret_cpp} {method_sig.name}({params_cpp}){const_qual} = 0;\n")
|
|
636
|
+
out.write(f"{INDENT}virtual ~{base_name}() = default;\n")
|
|
637
|
+
out.write("};\n")
|
|
638
|
+
|
|
639
|
+
@staticmethod
|
|
640
|
+
def emit_dynamic_base_forward_decl(out: TextIO, protocol: TpyProtocol) -> None:
|
|
641
|
+
"""Write `[template<...>] struct {Proto};` for a @dynamic protocol.
|
|
642
|
+
|
|
643
|
+
Two call sites: the concept declaration in this module (so the concept
|
|
644
|
+
body can name the protocol's own base struct inside a `convertible_to`
|
|
645
|
+
constraint) and the cycle-peer `_fwd.hpp` header (so peer translation
|
|
646
|
+
units can refer to the base by name without including the full header).
|
|
647
|
+
"""
|
|
648
|
+
cpp_name = protocol.name.replace(".", "::")
|
|
649
|
+
if protocol.type_params:
|
|
650
|
+
tparams = ", ".join(f"typename {tp}" for tp in protocol.type_params)
|
|
651
|
+
out.write(f"template<{tparams}> struct {cpp_name};\n")
|
|
652
|
+
else:
|
|
653
|
+
out.write(f"struct {cpp_name};\n")
|
|
654
|
+
|
|
655
|
+
def gen_dyn_protocol_base_trait(self, out: TextIO, protocol: TpyProtocol,
|
|
656
|
+
module_namespace: str) -> None:
|
|
657
|
+
"""Generate the ``tpy::is_dyn_protocol_base<P>`` specialization only.
|
|
658
|
+
|
|
659
|
+
Split from ``gen_dynamic_adapter_specs`` so it can be emitted early
|
|
660
|
+
(right after the protocol forward decl, before any in-module use that
|
|
661
|
+
would implicitly instantiate the primary template via ``Box<P>``,
|
|
662
|
+
``Adapter<P, T>``, etc.). The full Adapter / RefAdapter specs are
|
|
663
|
+
emitted later (see ``gen_dynamic_adapter_specs``) so their override
|
|
664
|
+
bodies see complete value types referenced in protocol method
|
|
665
|
+
signatures.
|
|
666
|
+
"""
|
|
667
|
+
protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
|
|
668
|
+
if protocol_info is None:
|
|
669
|
+
return
|
|
670
|
+
if protocol_info.cpp_concept:
|
|
671
|
+
return
|
|
672
|
+
qbase_name = f"{module_namespace}::{protocol.name}"
|
|
673
|
+
if protocol.type_params:
|
|
674
|
+
tparam_refs = ", ".join(protocol.type_params)
|
|
675
|
+
qbase = f"{qbase_name}<{tparam_refs}>"
|
|
676
|
+
tparam_only = "template<" + ", ".join(f"typename {tp}" for tp in protocol.type_params) + ">"
|
|
677
|
+
out.write(f"{tparam_only}\n")
|
|
678
|
+
out.write(f"struct tpy::is_dyn_protocol_base<{qbase}> : std::true_type {{}};\n\n")
|
|
679
|
+
else:
|
|
680
|
+
out.write(f"template<> struct tpy::is_dyn_protocol_base<{qbase_name}> : std::true_type {{}};\n\n")
|
|
681
|
+
|
|
682
|
+
def gen_dynamic_adapter_specs(self, out: TextIO, protocol: TpyProtocol,
|
|
683
|
+
module_namespace: str,
|
|
684
|
+
user_type_names: set[str] | None = None) -> None:
|
|
685
|
+
"""Generate ::tpy::Adapter and ::tpy::RefAdapter partial specializations.
|
|
686
|
+
|
|
687
|
+
Emitted at global scope (outside user namespace), following the EnumUtil
|
|
688
|
+
pattern. Uses fully-qualified names for concept and base class.
|
|
689
|
+
|
|
690
|
+
For generic @dynamic protocols, both adapters become class templates
|
|
691
|
+
parameterized on the protocol's type params + a concrete impl. The
|
|
692
|
+
concept constraint uses the C++20 abbreviated form `Concept<args>
|
|
693
|
+
Impl`, which desugars to `requires Concept<Impl, args>` -- matching
|
|
694
|
+
the concept signature `template<typename T_checked, typename _T0,
|
|
695
|
+
...>` emitted by gen_concept_decl.
|
|
696
|
+
|
|
697
|
+
``user_type_names`` is the set of record/protocol short names declared
|
|
698
|
+
in the current module; the adapter overrides qualify any bare
|
|
699
|
+
occurrence of these names with ``module_namespace::`` so they resolve
|
|
700
|
+
from inside the ``tpy::`` namespace (where the user namespace's
|
|
701
|
+
injected names are not visible, and for generic protocols the
|
|
702
|
+
dependent base class is not searched by unqualified lookup).
|
|
703
|
+
|
|
704
|
+
The ``tpy::is_dyn_protocol_base<P>`` trait specialization is emitted
|
|
705
|
+
separately by ``gen_dyn_protocol_base_trait`` (called earlier so
|
|
706
|
+
in-module uses of ``Box<P>`` resolve the trait correctly before the
|
|
707
|
+
Adapter spec lands).
|
|
708
|
+
"""
|
|
709
|
+
protocol_info = self.ctx.analyzer.registry.scan_by_short_name(protocol.name)
|
|
710
|
+
if protocol_info is None:
|
|
711
|
+
return
|
|
712
|
+
# @native + @dynamic: the runtime owns the abstract base and there
|
|
713
|
+
# is no codegen-emitted concept, so Adapter/RefAdapter specializations
|
|
714
|
+
# cannot be expressed here. Inheritance-only conformance covers the
|
|
715
|
+
# use case (Throwable: every concrete subclass inherits BaseException
|
|
716
|
+
# which inherits the runtime ::tpy::Throwable directly).
|
|
717
|
+
if protocol_info.cpp_concept:
|
|
718
|
+
return
|
|
719
|
+
|
|
720
|
+
all_methods = self.collect_concept_methods(protocol.name)
|
|
721
|
+
qbase_name = f"{module_namespace}::{protocol.name}"
|
|
722
|
+
qconcept = f"{module_namespace}::__{protocol.name}_Concept__"
|
|
723
|
+
|
|
724
|
+
if protocol.type_params:
|
|
725
|
+
impl = _ADAPTER_IMPL_GENERIC
|
|
726
|
+
if impl in protocol.type_params:
|
|
727
|
+
# Without this guard the redeclaration would surface only as a
|
|
728
|
+
# raw C++ error -- give the user a tpyc-level diagnostic.
|
|
729
|
+
raise CodeGenError(
|
|
730
|
+
f"@dynamic protocol '{protocol.name}' cannot declare a "
|
|
731
|
+
f"type parameter named '{impl}' (reserved by the "
|
|
732
|
+
f"@dynamic adapter codegen). Rename the type parameter "
|
|
733
|
+
f"(e.g. 'T').",
|
|
734
|
+
loc=protocol.loc,
|
|
735
|
+
)
|
|
736
|
+
tparam_decls = [f"typename {tp}" for tp in protocol.type_params]
|
|
737
|
+
tparam_refs = ", ".join(protocol.type_params)
|
|
738
|
+
tparam_decls.append(f"{qconcept}<{tparam_refs}> {impl}")
|
|
739
|
+
template_header = "template<" + ", ".join(tparam_decls) + ">"
|
|
740
|
+
qbase = f"{qbase_name}<{tparam_refs}>"
|
|
741
|
+
else:
|
|
742
|
+
impl = _ADAPTER_IMPL_NON_GENERIC
|
|
743
|
+
template_header = f"template<{qconcept} {impl}>"
|
|
744
|
+
qbase = qbase_name
|
|
745
|
+
|
|
746
|
+
# -- Owning adapter (for locals and rvalue call-site args) --
|
|
747
|
+
out.write(f"{template_header}\n")
|
|
748
|
+
out.write(f"struct tpy::Adapter<{qbase}, {impl}> : {qbase} {{\n")
|
|
749
|
+
out.write(f"{INDENT}{impl} inner;\n")
|
|
750
|
+
out.write(f"{INDENT}template<typename... Args>\n")
|
|
751
|
+
out.write(f"{INDENT}Adapter(Args&&... args) : inner(std::forward<Args>(args)...) {{}}\n")
|
|
752
|
+
self._gen_adapter_overrides(out, all_methods, protocol_info,
|
|
753
|
+
module_namespace, user_type_names)
|
|
754
|
+
out.write("};\n\n")
|
|
755
|
+
|
|
756
|
+
# -- Ref adapter (for lvalue call-site args, zero-copy) --
|
|
757
|
+
out.write(f"{template_header}\n")
|
|
758
|
+
out.write(f"struct tpy::RefAdapter<{qbase}, {impl}> : {qbase} {{\n")
|
|
759
|
+
out.write(f"{INDENT}{impl}& inner;\n")
|
|
760
|
+
out.write(f"{INDENT}RefAdapter({impl}& ref) : inner(ref) {{}}\n")
|
|
761
|
+
self._gen_adapter_overrides(out, all_methods, protocol_info,
|
|
762
|
+
module_namespace, user_type_names)
|
|
763
|
+
out.write("};\n")
|
|
764
|
+
|
|
765
|
+
def _gen_adapter_overrides(self, out: TextIO, all_methods: list[MethodSignature],
|
|
766
|
+
protocol_info: 'ProtocolInfo',
|
|
767
|
+
module_namespace: str = "",
|
|
768
|
+
user_type_names: set[str] | None = None) -> None:
|
|
769
|
+
"""Emit method override bodies shared by owning and ref adapters."""
|
|
770
|
+
for method_sig in all_methods:
|
|
771
|
+
ret_cpp = self._dynamic_return_type(method_sig)
|
|
772
|
+
const_qual = " const" if self._is_readonly_method(method_sig, protocol_info) else ""
|
|
773
|
+
params_cpp = self._dynamic_param_list(method_sig)
|
|
774
|
+
is_void = isinstance(method_sig.return_type, VoidType)
|
|
775
|
+
ret_kw = "" if is_void else "return "
|
|
776
|
+
call_expr = self._dynamic_forward_call(method_sig)
|
|
777
|
+
# Covariant str: inner may return string_view but vtable returns string;
|
|
778
|
+
# explicit construction handles the conversion.
|
|
779
|
+
if is_str_type(method_sig.return_type):
|
|
780
|
+
call_expr = f"std::string({call_expr})"
|
|
781
|
+
if user_type_names and module_namespace:
|
|
782
|
+
ret_cpp = self._qualify_user_types(ret_cpp, module_namespace, user_type_names)
|
|
783
|
+
params_cpp = self._qualify_user_types(params_cpp, module_namespace, user_type_names)
|
|
784
|
+
out.write(f"{INDENT}{ret_cpp} {method_sig.name}({params_cpp}){const_qual} override {{ {ret_kw}{call_expr}; }}\n")
|
|
785
|
+
|
|
786
|
+
@staticmethod
|
|
787
|
+
def _qualify_user_types(cpp_str: str, module_namespace: str,
|
|
788
|
+
user_type_names: set[str]) -> str:
|
|
789
|
+
"""Prefix bare occurrences of ``user_type_names`` with ``module_namespace::``.
|
|
790
|
+
|
|
791
|
+
Used for adapter override return/parameter types, which are rendered
|
|
792
|
+
inside ``tpy::`` and cannot rely on either the user namespace's scope
|
|
793
|
+
or (for generic protocols) the dependent base class's injected names.
|
|
794
|
+
The lookbehind skips names that already follow ``::`` or another
|
|
795
|
+
identifier character, leaving fully qualified or substring matches
|
|
796
|
+
untouched.
|
|
797
|
+
"""
|
|
798
|
+
import re
|
|
799
|
+
for name in user_type_names:
|
|
800
|
+
cpp_str = re.sub(
|
|
801
|
+
rf'(?<![:\w]){re.escape(name)}(?!\w)',
|
|
802
|
+
f'{module_namespace}::{name}',
|
|
803
|
+
cpp_str,
|
|
804
|
+
)
|
|
805
|
+
return cpp_str
|
|
806
|
+
|
|
807
|
+
def _is_readonly_method(self, method_sig: MethodSignature, protocol_info: 'ProtocolInfo') -> bool:
|
|
808
|
+
from ..typesys import ProtocolInfo as _PI
|
|
809
|
+
return method_sig.is_readonly or protocol_info.is_readonly
|
|
810
|
+
|
|
811
|
+
def _dynamic_return_type(self, method_sig: MethodSignature) -> str:
|
|
812
|
+
"""C++ return type for a dynamic dispatch method."""
|
|
813
|
+
if isinstance(method_sig.return_type, VoidType):
|
|
814
|
+
return "void"
|
|
815
|
+
return method_sig.return_type.to_cpp()
|
|
816
|
+
|
|
817
|
+
def _dynamic_param_list(self, method_sig: MethodSignature) -> str:
|
|
818
|
+
"""C++ parameter list for a dynamic dispatch method (excluding self)."""
|
|
819
|
+
parts = []
|
|
820
|
+
for pname, ptype in method_sig.params:
|
|
821
|
+
parts.append(ptype.to_cpp_param(pname))
|
|
822
|
+
return ", ".join(parts)
|
|
823
|
+
|
|
824
|
+
def _dynamic_forward_call(self, method_sig: MethodSignature) -> str:
|
|
825
|
+
"""Generate the forwarding call expression for an adapter method."""
|
|
826
|
+
arg_names = [pname for pname, _ in method_sig.params]
|
|
827
|
+
args_str = ", ".join(arg_names)
|
|
828
|
+
|
|
829
|
+
# Dunder methods with ::tpy:: free function equivalents
|
|
830
|
+
if method_sig.name == "__len__":
|
|
831
|
+
return f"::tpy::__len__(inner)"
|
|
832
|
+
if method_sig.name == "__getitem__" and len(method_sig.params) == 1:
|
|
833
|
+
return f"::tpy::__getitem__(inner, {args_str})"
|
|
834
|
+
if method_sig.name == "__setitem__" and len(method_sig.params) == 2:
|
|
835
|
+
return f"::tpy::__setitem__(inner, {args_str})"
|
|
836
|
+
if method_sig.name in DUNDER_TO_BINARY_OP and len(method_sig.params) == 1:
|
|
837
|
+
cpp_op = DUNDER_TO_BINARY_OP[method_sig.name]
|
|
838
|
+
return f"inner {cpp_op} {arg_names[0]}"
|
|
839
|
+
|
|
840
|
+
return f"inner.{method_sig.name}({args_str})"
|
|
841
|
+
|
|
842
|
+
def collect_record_types_from_type(self, typ: TpyType, result: set[str]) -> None:
|
|
843
|
+
"""Recursively collect all record type names from a type.
|
|
844
|
+
|
|
845
|
+
Codegen iterates `TpyProtocol.methods` (parser AST, not rewritten by
|
|
846
|
+
sema's `register_protocol`), so some NominalTypes here are still bare
|
|
847
|
+
parser placeholders (no `_module_qname`, no TypeDef entry). For those,
|
|
848
|
+
`is_user_record` is False; fall back to the record registry directly.
|
|
849
|
+
|
|
850
|
+
`@builtin_type` records (e.g. `tpy.coro.Waker`) are included too: when
|
|
851
|
+
they're TPy-defined in the current module, the forward-decl is
|
|
852
|
+
required just like a plain user record. The emit step filters by
|
|
853
|
+
`module_record_names`, so off-module builtin entries are dropped
|
|
854
|
+
there harmlessly.
|
|
855
|
+
"""
|
|
856
|
+
if isinstance(typ, NominalType) and not typ.is_protocol:
|
|
857
|
+
if typ.is_user_record:
|
|
858
|
+
result.add(typ.name)
|
|
859
|
+
else:
|
|
860
|
+
info = self.ctx.analyzer.registry.get_record(typ.name)
|
|
861
|
+
if info is not None:
|
|
862
|
+
result.add(typ.name)
|
|
863
|
+
for inner in typ.inner_types():
|
|
864
|
+
self.collect_record_types_from_type(inner, result)
|
|
865
|
+
|
|
866
|
+
def collect_type_args_of_bounded_records(
|
|
867
|
+
self,
|
|
868
|
+
typ: TpyType,
|
|
869
|
+
records_by_name: dict[str, TpyRecord],
|
|
870
|
+
result: set[str]
|
|
871
|
+
) -> None:
|
|
872
|
+
"""Collect record names used as type args to records with user-defined bounds.
|
|
873
|
+
|
|
874
|
+
When a protocol references Container[Message] where Container has a user-defined
|
|
875
|
+
protocol bound, C++ needs Message to be fully defined to check the constraint.
|
|
876
|
+
Same placeholder-tolerance story as `collect_record_types_from_type` --
|
|
877
|
+
accept either a resolved user-record NominalType or a bare placeholder
|
|
878
|
+
whose name resolves to a non-builtin RecordInfo in the registry.
|
|
879
|
+
"""
|
|
880
|
+
registry = self.ctx.analyzer.registry
|
|
881
|
+
|
|
882
|
+
def is_user_record_nominal(t: TpyType) -> bool:
|
|
883
|
+
if not isinstance(t, NominalType) or t.is_protocol:
|
|
884
|
+
return False
|
|
885
|
+
if t.is_user_record:
|
|
886
|
+
return True
|
|
887
|
+
info = registry.get_record(t.name)
|
|
888
|
+
return info is not None and info.builtin_type_key is None
|
|
889
|
+
|
|
890
|
+
if (isinstance(typ, NominalType) and typ.type_args
|
|
891
|
+
and is_user_record_nominal(typ)):
|
|
892
|
+
record = records_by_name.get(typ.name)
|
|
893
|
+
if record and record.type_param_bounds:
|
|
894
|
+
# Check if any bound is a user-defined protocol
|
|
895
|
+
has_user_bound = any(
|
|
896
|
+
(pi := protocol_info_of(bound)) is None or pi.cpp_concept is None
|
|
897
|
+
for bound in record.type_param_bounds.values()
|
|
898
|
+
)
|
|
899
|
+
if has_user_bound:
|
|
900
|
+
# Collect all type args as needing early definition
|
|
901
|
+
for type_arg in typ.type_args:
|
|
902
|
+
if isinstance(type_arg, NominalType) and is_user_record_nominal(type_arg):
|
|
903
|
+
result.add(type_arg.name)
|
|
904
|
+
|
|
905
|
+
# Recurse into inner types
|
|
906
|
+
for inner in typ.inner_types():
|
|
907
|
+
self.collect_type_args_of_bounded_records(inner, records_by_name, result)
|