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,744 @@
|
|
|
1
|
+
# tpy: cpp_namespace("tpystd::asyncio")
|
|
2
|
+
"""asyncio v1 -- minimum viable async runtime.
|
|
3
|
+
|
|
4
|
+
`run` / `sleep` / `create_task` / `Task[T]` / `Future[T]` / `Event` /
|
|
5
|
+
`CancelledError`. Lowers to `runtime/cpp/include/tpy/async.hpp` and
|
|
6
|
+
the TPy Executor in `_executor.py`. See `docs/ASYNC_DESIGN.md`.
|
|
7
|
+
"""
|
|
8
|
+
from builtins import BaseException, Exception, TimeoutError
|
|
9
|
+
from tpy import Own, Int32, CancelledError, Throwable, nocopy
|
|
10
|
+
from tpy.coro import (
|
|
11
|
+
Waker, Poll, Cancellable,
|
|
12
|
+
poll_ready, poll_pending, poll_ready_none,
|
|
13
|
+
)
|
|
14
|
+
from tpy.mem import UninitArrayStorage
|
|
15
|
+
from tplib import Box
|
|
16
|
+
from time import monotonic
|
|
17
|
+
from ._executor import (
|
|
18
|
+
Task, AnyTask,
|
|
19
|
+
task_from_coro, make_executor_owned_task, task_to_any_box,
|
|
20
|
+
Executor, _ExecutorScope,
|
|
21
|
+
_get_current_executor,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run[T](coro: Own[Cancellable[T]]) -> T:
|
|
26
|
+
if _get_current_executor() is not None:
|
|
27
|
+
raise RuntimeError(
|
|
28
|
+
"asyncio.run() cannot be called from a running event loop")
|
|
29
|
+
task = make_executor_owned_task[T](coro)
|
|
30
|
+
box = task_to_any_box[T](task)
|
|
31
|
+
_run_drain_main_task(box)
|
|
32
|
+
return task.__poll__(Waker()).value()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Lives here (not _executor.py): C++ function using-decls in
|
|
36
|
+
# asyncio.hpp require the source namespace already opened, which
|
|
37
|
+
# breaks under the parent-package include cycle.
|
|
38
|
+
def _run_drain_main_task(box: Own[Box[AnyTask]]) -> None:
|
|
39
|
+
executor = Executor()
|
|
40
|
+
scope = _ExecutorScope(executor)
|
|
41
|
+
main_id = executor.spawn(box)
|
|
42
|
+
try:
|
|
43
|
+
executor.run_until(main_id)
|
|
44
|
+
finally:
|
|
45
|
+
# Swallow drain-time exceptions; v1 has no place to surface them.
|
|
46
|
+
try:
|
|
47
|
+
executor.drain_spawned_with_cancel(main_id)
|
|
48
|
+
except BaseException:
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# No-op if no executor is running, so hand-rolled awaitables polled
|
|
53
|
+
# from a test harness without `asyncio.run` don't crash.
|
|
54
|
+
def _register_timer_at(deadline_seconds: float, waker: Waker) -> None:
|
|
55
|
+
handle = _get_current_executor()
|
|
56
|
+
if handle is None:
|
|
57
|
+
return
|
|
58
|
+
handle.register_timer(deadline_seconds, waker)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class SleepFuture:
|
|
62
|
+
"""Awaits a steady-clock deadline. Registers a timer with the
|
|
63
|
+
current executor on first poll and returns Pending until the
|
|
64
|
+
deadline elapses; conforms to the Awaitable[None] shape (poll +
|
|
65
|
+
`_cancel_pending` field that the runtime's `Task::cancel` flips).
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
deadline: float
|
|
69
|
+
registered: bool
|
|
70
|
+
_cancel_pending: bool
|
|
71
|
+
|
|
72
|
+
def __init__(self, seconds: float) -> None:
|
|
73
|
+
self.deadline = monotonic() + seconds
|
|
74
|
+
self.registered = False
|
|
75
|
+
self._cancel_pending = False
|
|
76
|
+
|
|
77
|
+
# Required for structural conformance to `@dynamic Cancellable[T]`
|
|
78
|
+
# (in `tpy.coro`). Mirrors the codegen-emitted `cancel()` on
|
|
79
|
+
# every generated coro struct.
|
|
80
|
+
def cancel(self) -> None:
|
|
81
|
+
self._cancel_pending = True
|
|
82
|
+
|
|
83
|
+
def __poll__(self, waker: Waker) -> Own[Poll[None]]:
|
|
84
|
+
if self._cancel_pending:
|
|
85
|
+
self._cancel_pending = False
|
|
86
|
+
# TODO: cancelling a registered SleepFuture leaves its timer
|
|
87
|
+
# entry in the executor's timer_heap + _timer_wakers. The
|
|
88
|
+
# generation guard makes the eventual wake a silent no-op,
|
|
89
|
+
# so it's harmless per-cancel, but the heap grows unbounded
|
|
90
|
+
# under cancel-heavy workloads. Fix: track the (timer_id,
|
|
91
|
+
# waker) pair on self at registration time and remove it
|
|
92
|
+
# from the executor here before throwing. Needs an executor
|
|
93
|
+
# `cancel_timer(timer_id)` primitive.
|
|
94
|
+
raise CancelledError()
|
|
95
|
+
if monotonic() >= self.deadline:
|
|
96
|
+
return poll_ready_none()
|
|
97
|
+
if not self.registered:
|
|
98
|
+
_register_timer_at(self.deadline, waker)
|
|
99
|
+
self.registered = True
|
|
100
|
+
return poll_pending()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Park the calling coroutine for `seconds` seconds. Returns an
|
|
104
|
+
# Own[Task[None]] whose underlying SleepFuture registers a timer with
|
|
105
|
+
# the current executor on first poll; the run loop wakes when the
|
|
106
|
+
# deadline arrives.
|
|
107
|
+
def sleep(seconds: float) -> Own[Task[None]]:
|
|
108
|
+
return task_from_coro[None](SleepFuture(seconds))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Spawn `coro` on the current executor and return an Own[Task[T]]
|
|
112
|
+
# handle. The task is registered with the executor's slot table so
|
|
113
|
+
# it runs concurrently with the spawning task. Raises RuntimeError if
|
|
114
|
+
# no event loop is running.
|
|
115
|
+
def create_task[T](coro: Own[Cancellable[T]]) -> Own[Task[T]]:
|
|
116
|
+
handle = _get_current_executor()
|
|
117
|
+
if handle is None:
|
|
118
|
+
raise RuntimeError(
|
|
119
|
+
"asyncio.create_task: no running event loop "
|
|
120
|
+
"(call asyncio.run(coro) to drive it)")
|
|
121
|
+
task = make_executor_owned_task[T](coro)
|
|
122
|
+
box = task_to_any_box[T](task)
|
|
123
|
+
slot_id = handle.spawn(box)
|
|
124
|
+
# Stamp a Waker for the slot onto the Task so `Task.cancel()` can
|
|
125
|
+
# mark the slot runnable promptly. Read the freshly-spawned slot's
|
|
126
|
+
# generation directly rather than hardcoding 0, so this stays
|
|
127
|
+
# correct if `Slot.__init__` ever changes its starting generation
|
|
128
|
+
# (e.g. for slot recycling). If the slot completes (generation
|
|
129
|
+
# bumps) before cancel fires, Waker.wake's generation guard
|
|
130
|
+
# silently drops the wake. Non-executor-owned tasks (built via
|
|
131
|
+
# `task_from_coro`) keep the default null-awaker Waker assigned in
|
|
132
|
+
# Task.__init__, and wake() is a safe no-op on those.
|
|
133
|
+
task._waker = handle.make_waker_for_slot(
|
|
134
|
+
slot_id, handle.slots[slot_id].generation)
|
|
135
|
+
return task
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@nocopy
|
|
139
|
+
class _WaitForFuture[T]:
|
|
140
|
+
"""Drives an inner Cancellable[T] against a steady-clock deadline.
|
|
141
|
+
|
|
142
|
+
Registers a one-shot timer with the current executor on first
|
|
143
|
+
poll. When the deadline elapses, propagates `cancel()` to the
|
|
144
|
+
inner frame and keeps polling it until it returns -- translating
|
|
145
|
+
the resulting `CancelledError` into `TimeoutError`. An external
|
|
146
|
+
cancel (outer task cancellation) propagates to the inner unchanged
|
|
147
|
+
and re-raises `CancelledError`. The deadline timer can fire after
|
|
148
|
+
the inner already completed; the generation guard in
|
|
149
|
+
`Executor.mark_runnable` swallows the late wake.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
_inner: Box[Cancellable[T]]
|
|
153
|
+
_deadline: float
|
|
154
|
+
_registered: bool
|
|
155
|
+
_cleanup: bool
|
|
156
|
+
_timed_out: bool
|
|
157
|
+
_cancel_pending: bool
|
|
158
|
+
|
|
159
|
+
def __init__(self, coro: Own[Cancellable[T]], timeout: float) -> None:
|
|
160
|
+
self._inner = Box[Cancellable[T]](coro)
|
|
161
|
+
self._deadline = monotonic() + timeout
|
|
162
|
+
self._registered = False
|
|
163
|
+
self._cleanup = False
|
|
164
|
+
self._timed_out = False
|
|
165
|
+
self._cancel_pending = False
|
|
166
|
+
|
|
167
|
+
# Required for `@dynamic Cancellable[T]` conformance. Flips the
|
|
168
|
+
# cancel flag; the next __poll__ propagates to the inner. Matches
|
|
169
|
+
# the SleepFuture / Future / Event pattern.
|
|
170
|
+
def cancel(self) -> None:
|
|
171
|
+
self._cancel_pending = True
|
|
172
|
+
|
|
173
|
+
def __poll__(self, waker: Waker) -> Own[Poll[T]]:
|
|
174
|
+
# Consume the cancel signal up front so a re-cancel arriving
|
|
175
|
+
# after `_cleanup=True` doesn't accumulate (cleanup is already
|
|
176
|
+
# in flight; the second cancel adds nothing). Mirrors the
|
|
177
|
+
# "consumed each poll" invariant SleepFuture maintains.
|
|
178
|
+
was_canceling = self._cancel_pending
|
|
179
|
+
self._cancel_pending = False
|
|
180
|
+
|
|
181
|
+
# Outer cancel wins over a pending timeout. Mark cleanup so
|
|
182
|
+
# an in-flight inner cancel observed below re-raises as
|
|
183
|
+
# CancelledError, not TimeoutError.
|
|
184
|
+
if was_canceling and not self._cleanup:
|
|
185
|
+
self._cleanup = True
|
|
186
|
+
self._inner.get().cancel()
|
|
187
|
+
|
|
188
|
+
if not self._registered:
|
|
189
|
+
_register_timer_at(self._deadline, waker)
|
|
190
|
+
self._registered = True
|
|
191
|
+
|
|
192
|
+
# Deadline elapsed and we have not started cleanup yet:
|
|
193
|
+
# propagate cancel to the inner and pump it until it returns.
|
|
194
|
+
# A non-positive timeout takes this branch on the first poll.
|
|
195
|
+
if not self._cleanup and monotonic() >= self._deadline:
|
|
196
|
+
self._cleanup = True
|
|
197
|
+
self._timed_out = True
|
|
198
|
+
self._inner.get().cancel()
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
return self._inner.get().__poll__(waker)
|
|
202
|
+
except CancelledError:
|
|
203
|
+
if self._timed_out:
|
|
204
|
+
raise TimeoutError()
|
|
205
|
+
raise
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# Await `coro` with a steady-clock deadline of `timeout` seconds.
|
|
209
|
+
# Returns the coroutine's value if it completes before the deadline.
|
|
210
|
+
# Otherwise propagates `cancel()` to the coroutine, pumps it until it
|
|
211
|
+
# observes the cancellation, then raises `TimeoutError`. A non-
|
|
212
|
+
# positive `timeout` triggers the deadline on the first poll (matches
|
|
213
|
+
# CPython).
|
|
214
|
+
#
|
|
215
|
+
# Outer cancellation of a task awaiting `wait_for` propagates through
|
|
216
|
+
# to the inner coroutine: the resume-case cancel-check (in every
|
|
217
|
+
# async-def coro frame) calls `cancel()` on the in-flight sub-coro
|
|
218
|
+
# before polling, so the inner observes `CancelledError` at its
|
|
219
|
+
# suspension point and can run `finally`-with-await cleanup before
|
|
220
|
+
# the cancellation surfaces to the caller.
|
|
221
|
+
async def wait_for[T](coro: Own[Cancellable[T]], timeout: float) -> T:
|
|
222
|
+
return await _WaitForFuture[T](coro, timeout)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@nocopy
|
|
226
|
+
class _GatherFuture[T]:
|
|
227
|
+
"""Drives N already-spawned `Task[T]`s concurrently and harvests
|
|
228
|
+
their results in input order.
|
|
229
|
+
|
|
230
|
+
Each `__poll__` cycle polls every still-unsettled task with the
|
|
231
|
+
awaiter's waker. When a sub-task completes, its TaskState wakes
|
|
232
|
+
that waker, scheduling another gather poll. On the first failure
|
|
233
|
+
(or outer cancel), transitions to cleanup mode: propagates
|
|
234
|
+
`cancel()` to remaining sub-tasks and discards their values as
|
|
235
|
+
they settle. Re-raises the first exception once every sub-task
|
|
236
|
+
has settled.
|
|
237
|
+
|
|
238
|
+
Cancel propagation is prompt for `create_task`-built sub-tasks:
|
|
239
|
+
`Task.cancel()` (since M10) fires a per-Task `Waker` stamped at
|
|
240
|
+
`create_task` time, marking the slot runnable so the in-flight
|
|
241
|
+
frame observes its `__cancel_pending` on its next poll rather
|
|
242
|
+
than waiting on a natural wake. Sub-tasks built via
|
|
243
|
+
`task_from_coro` (not registered with the executor) carry the
|
|
244
|
+
default null-awaker Waker, so cancel-wake is a no-op for them and
|
|
245
|
+
observation reverts to the sub-task's next natural progress --
|
|
246
|
+
the same latency that pre-M10 gather had.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
_tasks: list[Task[T]]
|
|
250
|
+
_completion_indices: list[Int32]
|
|
251
|
+
_completion_boxes: list[Box[T]]
|
|
252
|
+
_settled: list[bool]
|
|
253
|
+
_exc: Box[Throwable] | None
|
|
254
|
+
_completed: Int32
|
|
255
|
+
_cleanup: bool
|
|
256
|
+
_cancel_pending: bool
|
|
257
|
+
|
|
258
|
+
def __init__(self, tasks: list[Task[T]]) -> None:
|
|
259
|
+
# tasks is borrowed from the caller. Rc-clone each Task into
|
|
260
|
+
# our owned list so the future is self-contained -- avoids the
|
|
261
|
+
# codegen gap on passing a named-local Own[list[T]] arg into a
|
|
262
|
+
# sub-coro emplace (cheap: each clone is one refcount bump on
|
|
263
|
+
# the underlying TaskState).
|
|
264
|
+
self._tasks = []
|
|
265
|
+
self._completion_indices = []
|
|
266
|
+
self._completion_boxes = []
|
|
267
|
+
self._settled = []
|
|
268
|
+
self._exc = None
|
|
269
|
+
self._completed = 0
|
|
270
|
+
self._cleanup = False
|
|
271
|
+
self._cancel_pending = False
|
|
272
|
+
for t in tasks:
|
|
273
|
+
self._tasks.append(t.clone())
|
|
274
|
+
self._settled.append(False)
|
|
275
|
+
|
|
276
|
+
# Required for structural conformance to `@dynamic Cancellable[T]`.
|
|
277
|
+
# Mirrors the SleepFuture / _WaitForFuture pattern: just flips the
|
|
278
|
+
# signal; the next __poll__ propagates to sub-tasks.
|
|
279
|
+
def cancel(self) -> None:
|
|
280
|
+
self._cancel_pending = True
|
|
281
|
+
|
|
282
|
+
def __poll__(self, waker: Waker) -> Own[Poll[list[T]]]:
|
|
283
|
+
# Empty gather is trivially complete on every poll, including
|
|
284
|
+
# the first one after an outer cancel. Matches CPython's
|
|
285
|
+
# "gather() with no args returns []".
|
|
286
|
+
n = len(self._tasks)
|
|
287
|
+
if n == 0:
|
|
288
|
+
empty: list[T] = []
|
|
289
|
+
return poll_ready(empty)
|
|
290
|
+
|
|
291
|
+
# Consume cancel signal up front so a re-cancel arriving after
|
|
292
|
+
# cleanup is already in flight doesn't accumulate (mirrors
|
|
293
|
+
# _WaitForFuture). Propagate cancel into sub-tasks now; defer
|
|
294
|
+
# claiming `_exc` until AFTER the per-task loop so a freshly
|
|
295
|
+
# observable inner exception this cycle wins over the cancel
|
|
296
|
+
# (avoids silently dropping a real failure under racing-cancel).
|
|
297
|
+
was_canceling = self._cancel_pending
|
|
298
|
+
self._cancel_pending = False
|
|
299
|
+
if was_canceling and not self._cleanup:
|
|
300
|
+
self._cleanup = True
|
|
301
|
+
self._propagate_cancel()
|
|
302
|
+
|
|
303
|
+
# Poll every unsettled task with the shared waker. Multiple
|
|
304
|
+
# wakes between polls coalesce at the executor (one runnable
|
|
305
|
+
# flag per slot), so the O(N) re-poll per cycle is bounded.
|
|
306
|
+
i: Int32 = 0
|
|
307
|
+
while i < n:
|
|
308
|
+
if not self._settled[i]:
|
|
309
|
+
try:
|
|
310
|
+
p = self._tasks[i].__poll__(waker)
|
|
311
|
+
if p.is_ready():
|
|
312
|
+
self._settled[i] = True
|
|
313
|
+
self._completed += 1
|
|
314
|
+
if self._cleanup:
|
|
315
|
+
# Cleanup mode: discard value; `p` drops
|
|
316
|
+
# at end of block and the destructor
|
|
317
|
+
# disposes the contained T.
|
|
318
|
+
pass
|
|
319
|
+
else:
|
|
320
|
+
self._completion_indices.append(i)
|
|
321
|
+
self._completion_boxes.append(Box(p.value()))
|
|
322
|
+
except BaseException as e:
|
|
323
|
+
self._settled[i] = True
|
|
324
|
+
self._completed += 1
|
|
325
|
+
if self._exc is None:
|
|
326
|
+
self._exc = Box(e.clone())
|
|
327
|
+
if not self._cleanup:
|
|
328
|
+
self._cleanup = True
|
|
329
|
+
self._propagate_cancel()
|
|
330
|
+
i += 1
|
|
331
|
+
|
|
332
|
+
# Outer-cancel fallback: only claim `_exc` for CancelledError
|
|
333
|
+
# if nothing real fired this cycle (or any prior one).
|
|
334
|
+
if was_canceling and self._exc is None:
|
|
335
|
+
self._exc = Box(CancelledError())
|
|
336
|
+
|
|
337
|
+
if self._completed < n:
|
|
338
|
+
return poll_pending()
|
|
339
|
+
|
|
340
|
+
if self._exc is not None:
|
|
341
|
+
raise self._exc
|
|
342
|
+
|
|
343
|
+
# All sub-tasks succeeded. Reorder completions into input
|
|
344
|
+
# order. O(n^2) walk -- n is typically small (handful of
|
|
345
|
+
# concurrent operations); fine for v1.5.
|
|
346
|
+
result: list[T] = []
|
|
347
|
+
orig_i: Int32 = 0
|
|
348
|
+
while orig_i < n:
|
|
349
|
+
k: Int32 = 0
|
|
350
|
+
kn = len(self._completion_indices)
|
|
351
|
+
while k < kn:
|
|
352
|
+
if self._completion_indices[k] == orig_i:
|
|
353
|
+
box = self._completion_boxes.pop(k)
|
|
354
|
+
self._completion_indices.pop(k)
|
|
355
|
+
result.append(box.take())
|
|
356
|
+
break
|
|
357
|
+
k += 1
|
|
358
|
+
orig_i += 1
|
|
359
|
+
return poll_ready(result)
|
|
360
|
+
|
|
361
|
+
def _propagate_cancel(self) -> None:
|
|
362
|
+
i: Int32 = 0
|
|
363
|
+
n = len(self._tasks)
|
|
364
|
+
while i < n:
|
|
365
|
+
if not self._settled[i]:
|
|
366
|
+
self._tasks[i].cancel()
|
|
367
|
+
i += 1
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# Run `tasks` concurrently and return their results in input order.
|
|
371
|
+
# On the first sub-task exception (or outer cancellation), propagates
|
|
372
|
+
# `cancel()` to the remaining sub-tasks, waits for them to settle, then
|
|
373
|
+
# re-raises the first exception encountered.
|
|
374
|
+
#
|
|
375
|
+
# Two homogeneous entrypoints share the same `_GatherFuture[T]` engine:
|
|
376
|
+
# - `gather(*tasks)` -- variadic-positional form.
|
|
377
|
+
# - `gather_list(tasks)` -- list-shaped form.
|
|
378
|
+
# Both require all tasks to share return type `T`. The CPython-shape
|
|
379
|
+
# heterogeneous variadic form `gather[*Ts](*coros) -> tuple[*Ts]`
|
|
380
|
+
# remains deferred (needs variadic generics + the async-def `*args`
|
|
381
|
+
# codegen fix; see TODO.md / BUGS.md).
|
|
382
|
+
async def gather_list[T](tasks: list[Task[T]]) -> list[T]:
|
|
383
|
+
return await _GatherFuture[T](tasks)
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def gather[T](*tasks: Task[T]) -> Own[_GatherFuture[T]]:
|
|
387
|
+
# Sync factory returning the awaitable as Own[...] -- same shape as
|
|
388
|
+
# the hand-written `_WaitForFuture(coro, timeout)` / `SleepFuture(t)`
|
|
389
|
+
# awaitables, not the executor-registered `create_task` (gather does
|
|
390
|
+
# not spawn on the executor; the caller awaits the returned future
|
|
391
|
+
# directly). Each Task is Rc-cloned into an owned list so the
|
|
392
|
+
# awaitable is self-contained across the await point. _GatherFuture
|
|
393
|
+
# also re-clones internally; both bumps are O(1) refcount-only,
|
|
394
|
+
# kept for code simplicity.
|
|
395
|
+
#
|
|
396
|
+
# The explicit `for ... append` loop (not a list comprehension) is
|
|
397
|
+
# deliberate: iterating `tpy::varargs<Task[T]>` (non-value generic
|
|
398
|
+
# vararg) goes through codegen surface that BUGS.md notes as
|
|
399
|
+
# fragile in the comp/slicing form. Keep this shape.
|
|
400
|
+
task_list: list[Task[T]] = []
|
|
401
|
+
for t in tasks:
|
|
402
|
+
task_list.append(t.clone())
|
|
403
|
+
return _GatherFuture[T](task_list)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
@nocopy
|
|
407
|
+
class Settled[T]:
|
|
408
|
+
"""One slot in `gather_list_settled`'s output. Exactly one of
|
|
409
|
+
`value` / `exception` is populated; the other is `None`. The
|
|
410
|
+
record shape sidesteps two TPy/CPython mismatches around the
|
|
411
|
+
natural `T | BaseException` form: (1) TPy lowers union elements
|
|
412
|
+
in containers to a value-variant, and the exception root is a
|
|
413
|
+
polymorphic owner (slicing risk on by-value moves); (2) TPy
|
|
414
|
+
`isinstance(x, Box[Throwable])` against a union member of
|
|
415
|
+
that exact shape is not yet supported. Field access is a clean
|
|
416
|
+
discriminator on either side.
|
|
417
|
+
|
|
418
|
+
Callers inspect:
|
|
419
|
+
if entry.exception is not None:
|
|
420
|
+
raise entry.exception # recover dynamic type via except
|
|
421
|
+
elif entry.value is not None:
|
|
422
|
+
handle_ok(entry.value.get())
|
|
423
|
+
|
|
424
|
+
Both fields are wrapped in `Box` so the record stays trivially
|
|
425
|
+
movable inside `list[Settled[T]]` regardless of T's value/non-value
|
|
426
|
+
shape. The exception is stored as `Box[Throwable]` (the polymorphic
|
|
427
|
+
root); re-raise it to recover the concrete subclass.
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
value: Box[T] | None
|
|
431
|
+
exception: Box[Throwable] | None
|
|
432
|
+
|
|
433
|
+
# Users normally don't construct Settled directly --
|
|
434
|
+
# `gather_list_settled` is the only producer and assigns the
|
|
435
|
+
# `value` / `exception` fields directly post-init.
|
|
436
|
+
def __init__(self) -> None:
|
|
437
|
+
self.value = None
|
|
438
|
+
self.exception = None
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
@nocopy
|
|
442
|
+
class _GatherSettledFuture[T]:
|
|
443
|
+
"""Drives N already-spawned `Task[T]`s concurrently and harvests
|
|
444
|
+
every result -- value OR exception -- into a `list[Settled[T]]`
|
|
445
|
+
in input order. Variant of CPython
|
|
446
|
+
`gather(*coros, return_exceptions=True)`.
|
|
447
|
+
|
|
448
|
+
Unlike `_GatherFuture`, a sub-task exception does NOT cancel its
|
|
449
|
+
siblings: each task runs to completion and contributes either its
|
|
450
|
+
value or its raised exception to the result list. A sub-task that
|
|
451
|
+
is cancelled independently (via its own handle) raises
|
|
452
|
+
`CancelledError`, which is collected as a `Settled` entry like any
|
|
453
|
+
other exception -- matching CPython's `return_exceptions=True`
|
|
454
|
+
rule that a cancelled submitted task is treated as having raised.
|
|
455
|
+
|
|
456
|
+
Cancelling the gather itself (the task awaiting it) is different:
|
|
457
|
+
`cancel()` propagates cancel to every still-unsettled sub-task
|
|
458
|
+
(so they don't leak), but the `CancelledError` then propagates UP
|
|
459
|
+
to the awaiting caller -- it is NOT swallowed into the result
|
|
460
|
+
list. This also matches CPython: cancelling `gather()` cancels
|
|
461
|
+
it. (The collected-result path is therefore only observable for
|
|
462
|
+
independently-cancelled sub-tasks, not for cancelling the gather
|
|
463
|
+
caller.)
|
|
464
|
+
|
|
465
|
+
Storage uses arrival-order parallel arrays for both values and
|
|
466
|
+
exceptions (mirroring `_GatherFuture`'s shape). Boxes are
|
|
467
|
+
`@nocopy`; the assembly loop ownership-transfers each entry out
|
|
468
|
+
of its arrival array via `list.pop`, then assigns to the
|
|
469
|
+
appropriate `Settled` field. Per-input-index storage is rejected
|
|
470
|
+
here because `list[Box[T] | None]` element slots have no
|
|
471
|
+
take-and-clear primitive that satisfies the borrow checker.
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
_tasks: list[Task[T]]
|
|
475
|
+
_settled: list[bool]
|
|
476
|
+
_result_indices: list[Int32]
|
|
477
|
+
_result_boxes: list[Box[T]]
|
|
478
|
+
_exc_indices: list[Int32]
|
|
479
|
+
_exc_boxes: list[Box[Throwable]]
|
|
480
|
+
_completed: Int32
|
|
481
|
+
_cancel_pending: bool
|
|
482
|
+
|
|
483
|
+
def __init__(self, tasks: list[Task[T]]) -> None:
|
|
484
|
+
# `tasks` is borrowed from the caller -- Rc-clone each entry as
|
|
485
|
+
# in `_GatherFuture` (avoids the named-local Own[list[T]] arg
|
|
486
|
+
# codegen gap; each clone is one refcount bump on TaskState).
|
|
487
|
+
self._tasks = []
|
|
488
|
+
self._settled = []
|
|
489
|
+
self._result_indices = []
|
|
490
|
+
self._result_boxes = []
|
|
491
|
+
self._exc_indices = []
|
|
492
|
+
self._exc_boxes = []
|
|
493
|
+
self._completed = 0
|
|
494
|
+
self._cancel_pending = False
|
|
495
|
+
for t in tasks:
|
|
496
|
+
self._tasks.append(t.clone())
|
|
497
|
+
self._settled.append(False)
|
|
498
|
+
|
|
499
|
+
# Required for structural conformance to `@dynamic Cancellable[T]`.
|
|
500
|
+
def cancel(self) -> None:
|
|
501
|
+
self._cancel_pending = True
|
|
502
|
+
|
|
503
|
+
def __poll__(self, waker: Waker) -> Own[Poll[list[Settled[T]]]]:
|
|
504
|
+
n = len(self._tasks)
|
|
505
|
+
if n == 0:
|
|
506
|
+
empty: list[Settled[T]] = []
|
|
507
|
+
return poll_ready(empty)
|
|
508
|
+
|
|
509
|
+
# Outer cancel: mark every unsettled sub-task's slot runnable
|
|
510
|
+
# via Task.cancel. Subs will raise CancelledError at their
|
|
511
|
+
# next suspension; we collect it as an entry rather than
|
|
512
|
+
# bubbling it out (CPython's return_exceptions semantics).
|
|
513
|
+
was_canceling = self._cancel_pending
|
|
514
|
+
self._cancel_pending = False
|
|
515
|
+
if was_canceling:
|
|
516
|
+
i: Int32 = 0
|
|
517
|
+
while i < n:
|
|
518
|
+
if not self._settled[i]:
|
|
519
|
+
self._tasks[i].cancel()
|
|
520
|
+
i += 1
|
|
521
|
+
|
|
522
|
+
i: Int32 = 0
|
|
523
|
+
while i < n:
|
|
524
|
+
if not self._settled[i]:
|
|
525
|
+
try:
|
|
526
|
+
p = self._tasks[i].__poll__(waker)
|
|
527
|
+
if p.is_ready():
|
|
528
|
+
self._settled[i] = True
|
|
529
|
+
self._completed += 1
|
|
530
|
+
self._result_indices.append(i)
|
|
531
|
+
self._result_boxes.append(Box(p.value()))
|
|
532
|
+
except BaseException as e:
|
|
533
|
+
self._settled[i] = True
|
|
534
|
+
self._completed += 1
|
|
535
|
+
self._exc_indices.append(i)
|
|
536
|
+
self._exc_boxes.append(Box(e.clone()))
|
|
537
|
+
i += 1
|
|
538
|
+
|
|
539
|
+
if self._completed < n:
|
|
540
|
+
return poll_pending()
|
|
541
|
+
|
|
542
|
+
# Assemble in input order. For each input slot, scan the
|
|
543
|
+
# arrival-order arrays for a matching index, then `pop` to
|
|
544
|
+
# transfer ownership of the Box out of the list. O(n^2)
|
|
545
|
+
# walk -- N is typically small (handful of concurrent
|
|
546
|
+
# operations); fine for v1.5.
|
|
547
|
+
result: list[Settled[T]] = []
|
|
548
|
+
orig_i: Int32 = 0
|
|
549
|
+
while orig_i < n:
|
|
550
|
+
settled_via_value = False
|
|
551
|
+
k: Int32 = 0
|
|
552
|
+
kn = len(self._result_indices)
|
|
553
|
+
while k < kn:
|
|
554
|
+
if self._result_indices[k] == orig_i:
|
|
555
|
+
self._result_indices.pop(k)
|
|
556
|
+
box = self._result_boxes.pop(k)
|
|
557
|
+
entry = Settled[T]()
|
|
558
|
+
entry.value = box
|
|
559
|
+
result.append(entry)
|
|
560
|
+
settled_via_value = True
|
|
561
|
+
break
|
|
562
|
+
k += 1
|
|
563
|
+
if not settled_via_value:
|
|
564
|
+
k = 0
|
|
565
|
+
kn = len(self._exc_indices)
|
|
566
|
+
while k < kn:
|
|
567
|
+
if self._exc_indices[k] == orig_i:
|
|
568
|
+
self._exc_indices.pop(k)
|
|
569
|
+
ebox = self._exc_boxes.pop(k)
|
|
570
|
+
entry = Settled[T]()
|
|
571
|
+
entry.exception = ebox
|
|
572
|
+
result.append(entry)
|
|
573
|
+
break
|
|
574
|
+
k += 1
|
|
575
|
+
orig_i += 1
|
|
576
|
+
return poll_ready(result)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
# Run `tasks` concurrently and harvest every result -- value OR
|
|
580
|
+
# exception -- into `list[Settled[T]]` in input order. Variant of
|
|
581
|
+
# CPython `asyncio.gather(*coros, return_exceptions=True)`: a sub-task
|
|
582
|
+
# failure does NOT cancel siblings (each runs to completion); a
|
|
583
|
+
# sub-task cancelled independently is collected as a `Settled` entry
|
|
584
|
+
# with `exception` populated (its `CancelledError`). Cancelling the
|
|
585
|
+
# gather caller itself propagates cancel into the sub-tasks for
|
|
586
|
+
# cleanup and then re-raises `CancelledError` to the caller -- it is
|
|
587
|
+
# NOT swallowed into the result list (matching CPython: cancelling
|
|
588
|
+
# gather() cancels it).
|
|
589
|
+
#
|
|
590
|
+
# TPy-specific shape: returns `list[Settled[T]]` rather than CPython's
|
|
591
|
+
# `list[T | BaseException]`. Two reasons: (1) TPy lowers union
|
|
592
|
+
# elements in containers to a value-variant and the exception root is
|
|
593
|
+
# a polymorphic owner (slicing risk); (2) `isinstance` against
|
|
594
|
+
# `Box[Throwable]` as a union member isn't yet supported. The
|
|
595
|
+
# `Settled[T]` record gives clean field-based discrimination; the
|
|
596
|
+
# exception is a `Box[Throwable]` (re-raise to recover the subclass).
|
|
597
|
+
#
|
|
598
|
+
# Sibling of `gather_list` / `gather` (cancel-and-re-raise variants).
|
|
599
|
+
async def gather_list_settled[T](
|
|
600
|
+
tasks: list[Task[T]]) -> list[Settled[T]]:
|
|
601
|
+
return await _GatherSettledFuture[T](tasks)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
class InvalidStateError(Exception):
|
|
605
|
+
"""Raised when set_result / set_exception is called on a Future
|
|
606
|
+
that is already done."""
|
|
607
|
+
pass
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
class Future[T]:
|
|
611
|
+
"""Single-awaiter, manual-completion awaitable.
|
|
612
|
+
|
|
613
|
+
Construct, await it from a coroutine, then call `set_result(value)`
|
|
614
|
+
(or `set_exception(exc)`) from another context to complete it.
|
|
615
|
+
|
|
616
|
+
v1 limitations:
|
|
617
|
+
- Single-awaiter: a second waiter raises ValueError.
|
|
618
|
+
- poll() consumes the result: a second poll after Ready panics
|
|
619
|
+
(or wakes a future poll on the empty storage). Multi-awaiter v2
|
|
620
|
+
will keep the result around.
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
_done: bool
|
|
624
|
+
_has_waiter: bool
|
|
625
|
+
_has_result: bool
|
|
626
|
+
_exception: Box[Throwable] | None
|
|
627
|
+
_waiter: Waker
|
|
628
|
+
_result: UninitArrayStorage[T, 1]
|
|
629
|
+
|
|
630
|
+
def __init__(self) -> None:
|
|
631
|
+
self._done = False
|
|
632
|
+
self._has_waiter = False
|
|
633
|
+
self._has_result = False
|
|
634
|
+
self._exception = None
|
|
635
|
+
self._waiter = Waker()
|
|
636
|
+
self._result = UninitArrayStorage[T, 1]()
|
|
637
|
+
|
|
638
|
+
def __del__(self) -> None:
|
|
639
|
+
if self._has_result:
|
|
640
|
+
self._result.take0()
|
|
641
|
+
self._has_result = False
|
|
642
|
+
|
|
643
|
+
def done(self) -> bool:
|
|
644
|
+
return self._done
|
|
645
|
+
|
|
646
|
+
def set_result(self, value: Own[T]) -> None:
|
|
647
|
+
# Takes ownership of `value` so nocopy types work without a
|
|
648
|
+
# `__copy__` opt-in. Callers passing a named local must use
|
|
649
|
+
# `tpy.copy(x)` explicitly to keep their reference alive.
|
|
650
|
+
if self._done:
|
|
651
|
+
raise InvalidStateError("Future already done")
|
|
652
|
+
self._result.init0(value)
|
|
653
|
+
self._has_result = True
|
|
654
|
+
self._done = True
|
|
655
|
+
if self._has_waiter:
|
|
656
|
+
self._waiter.wake()
|
|
657
|
+
self._has_waiter = False
|
|
658
|
+
|
|
659
|
+
def set_exception(self, exc: Own[Throwable]) -> None:
|
|
660
|
+
if self._done:
|
|
661
|
+
raise InvalidStateError("Future already done")
|
|
662
|
+
self._exception = Box(exc)
|
|
663
|
+
self._done = True
|
|
664
|
+
if self._has_waiter:
|
|
665
|
+
self._waiter.wake()
|
|
666
|
+
self._has_waiter = False
|
|
667
|
+
|
|
668
|
+
# Required for structural conformance to `@dynamic Cancellable[T]`
|
|
669
|
+
# (in `tpy.coro`). Future cancellation is task-level: the
|
|
670
|
+
# awaiting Task throws CancelledError before re-polling the Future,
|
|
671
|
+
# so the Future itself has no inner state to flip. This is the
|
|
672
|
+
# protocol hook called via the type-erased Adapter; the body is
|
|
673
|
+
# intentionally a no-op.
|
|
674
|
+
def cancel(self) -> None:
|
|
675
|
+
pass
|
|
676
|
+
|
|
677
|
+
def __poll__(self, waker: Waker) -> Own[Poll[T]]:
|
|
678
|
+
if self._done:
|
|
679
|
+
if self._exception is not None:
|
|
680
|
+
raise self._exception
|
|
681
|
+
if not self._has_result:
|
|
682
|
+
raise ValueError(
|
|
683
|
+
"Future done with no result and no exception")
|
|
684
|
+
self._has_result = False
|
|
685
|
+
# take0() returns Own[T] -- pass directly as rvalue so
|
|
686
|
+
# nocopy types (T with __del__ but no __copy__) flow through
|
|
687
|
+
# without requiring a copy ctor.
|
|
688
|
+
return poll_ready(self._result.take0())
|
|
689
|
+
if self._has_waiter:
|
|
690
|
+
raise ValueError(
|
|
691
|
+
"Future already has a waiter (single-awaiter v1)")
|
|
692
|
+
self._waiter = waker
|
|
693
|
+
self._has_waiter = True
|
|
694
|
+
return poll_pending()
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
class Event:
|
|
698
|
+
"""Boolean completion signal -- the no-payload analog of Future.
|
|
699
|
+
Single-awaiter v1.
|
|
700
|
+
|
|
701
|
+
`set` / `clear` / `is_set` match CPython. Divergence: TPy's Event
|
|
702
|
+
is directly awaitable (`await event`) where CPython requires
|
|
703
|
+
`await event.wait()` -- TPy classes can't yet define `async def`
|
|
704
|
+
methods, so tests using Event need `no_cpython.txt`.
|
|
705
|
+
"""
|
|
706
|
+
|
|
707
|
+
_is_set: bool
|
|
708
|
+
_has_waiter: bool
|
|
709
|
+
_waiter: Waker
|
|
710
|
+
|
|
711
|
+
def __init__(self) -> None:
|
|
712
|
+
self._is_set = False
|
|
713
|
+
self._has_waiter = False
|
|
714
|
+
self._waiter = Waker()
|
|
715
|
+
|
|
716
|
+
def is_set(self) -> bool:
|
|
717
|
+
return self._is_set
|
|
718
|
+
|
|
719
|
+
def set(self) -> None:
|
|
720
|
+
if self._is_set:
|
|
721
|
+
return
|
|
722
|
+
self._is_set = True
|
|
723
|
+
if self._has_waiter:
|
|
724
|
+
self._waiter.wake()
|
|
725
|
+
self._has_waiter = False
|
|
726
|
+
|
|
727
|
+
def clear(self) -> None:
|
|
728
|
+
self._is_set = False
|
|
729
|
+
|
|
730
|
+
# Required for structural conformance to `@dynamic Cancellable[T]`
|
|
731
|
+
# (in `tpy.coro`). Event cancellation is task-level (see
|
|
732
|
+
# Future.cancel above for the rationale); body is a no-op.
|
|
733
|
+
def cancel(self) -> None:
|
|
734
|
+
pass
|
|
735
|
+
|
|
736
|
+
def __poll__(self, waker: Waker) -> Own[Poll[None]]:
|
|
737
|
+
if self._is_set:
|
|
738
|
+
return poll_ready_none()
|
|
739
|
+
if self._has_waiter:
|
|
740
|
+
raise ValueError(
|
|
741
|
+
"Event already has a waiter (single-awaiter v1)")
|
|
742
|
+
self._waiter = waker
|
|
743
|
+
self._has_waiter = True
|
|
744
|
+
return poll_pending()
|