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,1729 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conformance tests for the TypeDef registry.
|
|
3
|
+
|
|
4
|
+
Two layers:
|
|
5
|
+
|
|
6
|
+
1. Per-qname conformance (Phase A gate): for each qname in the registry,
|
|
7
|
+
assert TypeDef fields agree with what the corresponding TpyType
|
|
8
|
+
instance returns. Catches "registry forgot about X" before the
|
|
9
|
+
subclass is deleted. Once the subclass is gone, the instance and
|
|
10
|
+
registry read from the same source and the check is tautological --
|
|
11
|
+
but it costs nothing to keep.
|
|
12
|
+
|
|
13
|
+
2. Primitive hard-coded snapshot (Phase D step 0): an explicit golden
|
|
14
|
+
table of every primitive's intrinsic per-qname behavior, compared
|
|
15
|
+
against the live instance. This is *independent* validation --
|
|
16
|
+
hard-coded answers that remain valid after the primitive subclass is
|
|
17
|
+
deleted and the instance starts reading from the registry. The
|
|
18
|
+
snapshot pins what Phase D must preserve.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
from tpyc import typesys as ts
|
|
26
|
+
from tpyc.type_def_registry import (
|
|
27
|
+
TypeCategory,
|
|
28
|
+
get_type_def,
|
|
29
|
+
type_def_of,
|
|
30
|
+
resolve_send_sync,
|
|
31
|
+
_type_defs,
|
|
32
|
+
)
|
|
33
|
+
from tpyc.type_def_registry import IntTraits, FloatTraits
|
|
34
|
+
from tpyc.typesys import ALL_FIXED_INTS
|
|
35
|
+
from tpyc.parse.parser import _FIXED_INT_NAMES as PARSER_FIXED_INT_NAMES
|
|
36
|
+
from tpyc.codegen_cpp.statements import StatementGenerator
|
|
37
|
+
from tpyc.macro_api import _FIXED_INT_NAMES as MACRO_FIXED_INT_NAMES
|
|
38
|
+
from tpyc.compilation_context import activate_compiler
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Map qname -> a concrete TpyType instance with canonical args.
|
|
42
|
+
# Primitives resolve to the typesys singleton. Containers get Int32 /
|
|
43
|
+
# (str, Int32) / (Int32, 10) as their type args.
|
|
44
|
+
def _canonical_instances() -> dict[str, ts.TpyType]:
|
|
45
|
+
I32 = ts.INT32
|
|
46
|
+
STR = ts.STR
|
|
47
|
+
cases: dict[str, ts.TpyType] = {
|
|
48
|
+
"tpy.Int8": ts.INT8, "tpy.Int16": ts.INT16,
|
|
49
|
+
"tpy.Int32": ts.INT32, "tpy.Int64": ts.INT64,
|
|
50
|
+
"tpy.UInt8": ts.UINT8, "tpy.UInt16": ts.UINT16,
|
|
51
|
+
"tpy.UInt32": ts.UINT32, "tpy.UInt64": ts.UINT64,
|
|
52
|
+
"builtins.int": ts.BIGINT,
|
|
53
|
+
"builtins.float": ts.FLOAT,
|
|
54
|
+
"tpy.Float32": ts.FLOAT32,
|
|
55
|
+
"builtins.bool": ts.BOOL,
|
|
56
|
+
"tpy.Char": ts.CHAR,
|
|
57
|
+
"builtins.str": ts.STR,
|
|
58
|
+
"tpy.String": ts.STRING,
|
|
59
|
+
"tpy.StrView": ts.STRVIEW,
|
|
60
|
+
"tpy.FStr": ts.FSTR,
|
|
61
|
+
"builtins.bytes": ts.BYTES,
|
|
62
|
+
"builtins.bytearray": ts.BYTEARRAY,
|
|
63
|
+
"tpy.BytesView": ts.BYTESVIEW,
|
|
64
|
+
"tpy.basic_slice": ts.BASIC_SLICE,
|
|
65
|
+
"builtins.slice": ts.SLICE,
|
|
66
|
+
"builtins.list": ts.make_list(I32),
|
|
67
|
+
"builtins.dict": ts.make_dict(STR, I32),
|
|
68
|
+
"builtins.set": ts.make_set(I32),
|
|
69
|
+
"builtins.dict_keys": ts.make_dict_keys_view(STR, I32),
|
|
70
|
+
"builtins.dict_values": ts.make_dict_values_view(STR, I32),
|
|
71
|
+
"builtins.dict_items": ts.make_dict_items_view(STR, I32),
|
|
72
|
+
"builtins.Range": ts.make_range(I32),
|
|
73
|
+
"tpy.Array": ts.make_array(I32, 10),
|
|
74
|
+
"tpy.Span": ts.make_span(I32),
|
|
75
|
+
"tpy.SpanIter": ts.make_span_iter(I32),
|
|
76
|
+
"tpy.CopyIter": ts.make_copy_iter(I32),
|
|
77
|
+
"tpy.OwnIter": ts.make_own_iter(I32),
|
|
78
|
+
"tpy.Ptr": ts.PtrType(I32),
|
|
79
|
+
"tpy.coro.Waker": ts.WAKER,
|
|
80
|
+
}
|
|
81
|
+
return cases
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@pytest.fixture(scope="module")
|
|
85
|
+
def instances() -> dict[str, ts.TpyType]:
|
|
86
|
+
return _canonical_instances()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_every_registered_qname_has_a_canonical_instance(instances):
|
|
90
|
+
"""Every qname in the registry must have a canonical instance in the
|
|
91
|
+
test so the conformance assertions actually cover it."""
|
|
92
|
+
missing = set(_type_defs) - set(instances)
|
|
93
|
+
assert not missing, f"Registry qnames without canonical instance: {missing}"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_type_def_of_matches_registry(instances):
|
|
97
|
+
for qname, inst in instances.items():
|
|
98
|
+
td = type_def_of(inst)
|
|
99
|
+
assert td is not None, f"type_def_of returned None for {qname}"
|
|
100
|
+
assert td.qname == qname, (
|
|
101
|
+
f"qname mismatch: instance.qualified_name()={inst.qualified_name()!r} "
|
|
102
|
+
f"lookup returned TypeDef({td.qname!r})"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_is_value_type_matches_subclass(instances):
|
|
107
|
+
for qname, inst in instances.items():
|
|
108
|
+
td = get_type_def(qname)
|
|
109
|
+
assert td is not None
|
|
110
|
+
assert td.is_value_type == inst.is_value_type(), (
|
|
111
|
+
f"{qname}: TypeDef.is_value_type={td.is_value_type} "
|
|
112
|
+
f"but instance.is_value_type()={inst.is_value_type()}"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_subscript_borrows_matches_subclass(instances):
|
|
117
|
+
for qname, inst in instances.items():
|
|
118
|
+
td = get_type_def(qname)
|
|
119
|
+
assert td is not None
|
|
120
|
+
assert td.subscript_borrows == inst.subscript_borrows(), (
|
|
121
|
+
f"{qname}: TypeDef.subscript_borrows={td.subscript_borrows} "
|
|
122
|
+
f"but instance.subscript_borrows()={inst.subscript_borrows()}"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_is_send_override_matches_subclass(instances):
|
|
127
|
+
for qname, inst in instances.items():
|
|
128
|
+
td = get_type_def(qname)
|
|
129
|
+
assert td is not None
|
|
130
|
+
if td.is_send is not None:
|
|
131
|
+
resolved = resolve_send_sync(td.is_send, inst.type_args)
|
|
132
|
+
assert resolved == inst.is_send(), (
|
|
133
|
+
f"{qname}: TypeDef.is_send resolved={resolved} but "
|
|
134
|
+
f"instance.is_send()={inst.is_send()}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_is_sync_override_matches_subclass(instances):
|
|
139
|
+
for qname, inst in instances.items():
|
|
140
|
+
td = get_type_def(qname)
|
|
141
|
+
assert td is not None
|
|
142
|
+
if td.is_sync is not None:
|
|
143
|
+
resolved = resolve_send_sync(td.is_sync, inst.type_args)
|
|
144
|
+
assert resolved == inst.is_sync(), (
|
|
145
|
+
f"{qname}: TypeDef.is_sync resolved={resolved} but "
|
|
146
|
+
f"instance.is_sync()={inst.is_sync()}"
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_cpp_formatter_matches_subclass(instances):
|
|
151
|
+
for qname, inst in instances.items():
|
|
152
|
+
td = get_type_def(qname)
|
|
153
|
+
assert td is not None
|
|
154
|
+
if td.cpp_formatter is not None:
|
|
155
|
+
args = inst.type_args
|
|
156
|
+
assert td.cpp_formatter(args) == inst.to_cpp(), (
|
|
157
|
+
f"{qname}: TypeDef.cpp_formatter({args!r})="
|
|
158
|
+
f"{td.cpp_formatter(args)!r} but "
|
|
159
|
+
f"instance.to_cpp()={inst.to_cpp()!r}"
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_predicate_functions_agree_with_isinstance(instances):
|
|
164
|
+
from tpyc.type_def_registry import (
|
|
165
|
+
is_list, is_dict, is_set, is_array, is_span, is_dict_view,
|
|
166
|
+
is_range, is_iterator_adapter,
|
|
167
|
+
)
|
|
168
|
+
# No dedicated subclasses remain for nominal container types; all predicates
|
|
169
|
+
# are keyed on qname.
|
|
170
|
+
for qname, inst in instances.items():
|
|
171
|
+
assert is_set(inst) == (qname == "builtins.set"), (
|
|
172
|
+
f"is_set({qname}) = {is_set(inst)}"
|
|
173
|
+
)
|
|
174
|
+
assert is_dict(inst) == (qname == "builtins.dict"), (
|
|
175
|
+
f"is_dict({qname}) = {is_dict(inst)}"
|
|
176
|
+
)
|
|
177
|
+
assert is_array(inst) == (qname == "tpy.Array"), (
|
|
178
|
+
f"is_array({qname}) = {is_array(inst)}"
|
|
179
|
+
)
|
|
180
|
+
assert is_span(inst) == (qname == "tpy.Span"), (
|
|
181
|
+
f"is_span({qname}) = {is_span(inst)}"
|
|
182
|
+
)
|
|
183
|
+
assert is_range(inst) == (qname == "builtins.Range"), (
|
|
184
|
+
f"is_range({qname}) = {is_range(inst)}"
|
|
185
|
+
)
|
|
186
|
+
assert is_list(inst) == (qname == "builtins.list"), (
|
|
187
|
+
f"is_list({qname}) = {is_list(inst)}"
|
|
188
|
+
)
|
|
189
|
+
expected = qname in ("builtins.dict_keys", "builtins.dict_values", "builtins.dict_items")
|
|
190
|
+
assert is_dict_view(inst) == expected, (
|
|
191
|
+
f"is_dict_view({qname}) = {is_dict_view(inst)} but expected {expected}"
|
|
192
|
+
)
|
|
193
|
+
# iterator adapters: SpanIter + CopyIter + OwnIter (qname-based check)
|
|
194
|
+
for qname, inst in instances.items():
|
|
195
|
+
expected = qname in ("tpy.SpanIter", "tpy.CopyIter", "tpy.OwnIter")
|
|
196
|
+
assert is_iterator_adapter(inst) == expected, (
|
|
197
|
+
f"is_iterator_adapter({qname}) = {is_iterator_adapter(inst)} but expected {expected}"
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_pending_types_are_not_concrete_containers():
|
|
202
|
+
"""PendingListType / PendingDictType / PendingSetType share builtin qnames
|
|
203
|
+
with their resolved form ("builtins.list" etc.) but are distinct TpyType
|
|
204
|
+
subclasses with their own fields. `is_list` / `is_dict` / `is_set` must
|
|
205
|
+
reject them so call sites that access `type_args[0]` don't crash. Regression
|
|
206
|
+
test for the `list({})` crash (PendingDictType passing `is_dict`).
|
|
207
|
+
"""
|
|
208
|
+
from tpyc.type_def_registry import is_list, is_dict, is_set
|
|
209
|
+
pending_list = ts.PendingListType(ts.INT32, 0, 0)
|
|
210
|
+
pending_dict = ts.PendingDictType(ts.STR, ts.INT32, 0)
|
|
211
|
+
pending_set = ts.PendingSetType(ts.INT32, 0)
|
|
212
|
+
|
|
213
|
+
# qnames match their resolved form (intentional) ...
|
|
214
|
+
assert pending_list.qualified_name() == "builtins.list"
|
|
215
|
+
assert pending_dict.qualified_name() == "builtins.dict"
|
|
216
|
+
assert pending_set.qualified_name() == "builtins.set"
|
|
217
|
+
|
|
218
|
+
# ... but the predicates must still reject them
|
|
219
|
+
assert not is_list(pending_list), "is_list must reject PendingListType"
|
|
220
|
+
assert not is_dict(pending_dict), "is_dict must reject PendingDictType"
|
|
221
|
+
assert not is_set(pending_set), "is_set must reject PendingSetType"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_pending_types_do_not_inherit_registry_behavior():
|
|
225
|
+
"""Pending* types delegate `qualified_name()` to their resolved builtin,
|
|
226
|
+
so `type_def_of(pending_list)` returns the `list` TypeDef. But base
|
|
227
|
+
`TpyType` methods (subscript_borrows, needs_explicit_element_target,
|
|
228
|
+
is_value_type, is_expensive_copy, param_needs_copy_for_reassign)
|
|
229
|
+
must NOT inherit that registry behavior -- pending types haven't been
|
|
230
|
+
resolved yet and should report class defaults. Regression test for
|
|
231
|
+
the `_nominal_td` guard on the base class methods.
|
|
232
|
+
"""
|
|
233
|
+
pending_list = ts.PendingListType(ts.INT32, 0, 0)
|
|
234
|
+
pending_dict = ts.PendingDictType(ts.STR, ts.INT32, 0)
|
|
235
|
+
pending_set = ts.PendingSetType(ts.INT32, 0)
|
|
236
|
+
|
|
237
|
+
resolved_list = ts.make_list(ts.INT32)
|
|
238
|
+
# Baseline: the resolved form DOES pick up registry behavior.
|
|
239
|
+
assert resolved_list.subscript_borrows() is True
|
|
240
|
+
|
|
241
|
+
# The pending form must NOT pick it up despite sharing the qname.
|
|
242
|
+
for pending in (pending_list, pending_dict, pending_set):
|
|
243
|
+
assert pending.subscript_borrows() is False, (
|
|
244
|
+
f"{type(pending).__name__}.subscript_borrows() leaked "
|
|
245
|
+
f"list/dict/set TypeDef value"
|
|
246
|
+
)
|
|
247
|
+
assert pending.needs_explicit_element_target() is False
|
|
248
|
+
assert pending.is_value_type() is False
|
|
249
|
+
assert pending.is_expensive_copy() is False
|
|
250
|
+
assert pending.param_needs_copy_for_reassign() is False
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# =========================================================================
|
|
254
|
+
# Primitive hard-coded snapshot (Phase D step 0).
|
|
255
|
+
#
|
|
256
|
+
# Captures intrinsic per-qname behavior of every primitive currently
|
|
257
|
+
# implemented as a dedicated TpyType subclass. The snapshot is the golden
|
|
258
|
+
# reference for Phase D: as the subclasses are collapsed into NominalType +
|
|
259
|
+
# TypeDef, the instance methods start reading from the registry, but the
|
|
260
|
+
# hard-coded expected values here stay unchanged -- which makes this an
|
|
261
|
+
# independent validation that Phase D didn't drift behavior.
|
|
262
|
+
#
|
|
263
|
+
# Keep this table hand-written. Regenerating it from the instances would
|
|
264
|
+
# defeat the whole point (the check would pass by definition).
|
|
265
|
+
# =========================================================================
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# Sentinel -- FStr.to_cpp() raises TypeError (compile-time only type).
|
|
269
|
+
_RAISES_TYPE_ERROR = object()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# Each entry pins the answers expected from the live TpyType instance *and*,
|
|
273
|
+
# eventually, from the TypeDef registry when the primitive becomes a plain
|
|
274
|
+
# NominalType. Fields mirror the current subclass overrides:
|
|
275
|
+
#
|
|
276
|
+
# category -- TypeCategory enum value
|
|
277
|
+
# is_value_type -- bool
|
|
278
|
+
# is_send / is_sync -- bool (default: == is_value_type)
|
|
279
|
+
# subscript_borrows -- bool (primitives: always False today)
|
|
280
|
+
# is_expensive_copy -- bool
|
|
281
|
+
# param_needs_copy_for_reassign-- bool
|
|
282
|
+
# is_compile_time_only -- bool
|
|
283
|
+
# to_cpp -- str or _RAISES_TYPE_ERROR
|
|
284
|
+
# to_cpp_param_type -- str (default: == to_cpp for value types)
|
|
285
|
+
# element_qname -- str | None (qname of get_element_type())
|
|
286
|
+
# int_bits / int_signed -- only for FIXED_INT category
|
|
287
|
+
# float_bits -- only for FLOAT category
|
|
288
|
+
|
|
289
|
+
PRIMITIVE_SNAPSHOT: dict[str, dict] = {
|
|
290
|
+
# --- Fixed-width integers --------------------------------------------
|
|
291
|
+
"tpy.Int8": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
292
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
293
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
294
|
+
is_compile_time_only=False,
|
|
295
|
+
to_cpp="int8_t", to_cpp_param_type="int8_t",
|
|
296
|
+
element_qname=None, int_bits=8, int_signed=True),
|
|
297
|
+
"tpy.Int16": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
298
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
299
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
300
|
+
is_compile_time_only=False,
|
|
301
|
+
to_cpp="int16_t", to_cpp_param_type="int16_t",
|
|
302
|
+
element_qname=None, int_bits=16, int_signed=True),
|
|
303
|
+
"tpy.Int32": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
304
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
305
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
306
|
+
is_compile_time_only=False,
|
|
307
|
+
to_cpp="int32_t", to_cpp_param_type="int32_t",
|
|
308
|
+
element_qname=None, int_bits=32, int_signed=True),
|
|
309
|
+
"tpy.Int64": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
310
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
311
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
312
|
+
is_compile_time_only=False,
|
|
313
|
+
to_cpp="int64_t", to_cpp_param_type="int64_t",
|
|
314
|
+
element_qname=None, int_bits=64, int_signed=True),
|
|
315
|
+
"tpy.UInt8": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
316
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
317
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
318
|
+
is_compile_time_only=False,
|
|
319
|
+
to_cpp="uint8_t", to_cpp_param_type="uint8_t",
|
|
320
|
+
element_qname=None, int_bits=8, int_signed=False),
|
|
321
|
+
"tpy.UInt16": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
322
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
323
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
324
|
+
is_compile_time_only=False,
|
|
325
|
+
to_cpp="uint16_t", to_cpp_param_type="uint16_t",
|
|
326
|
+
element_qname=None, int_bits=16, int_signed=False),
|
|
327
|
+
"tpy.UInt32": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
328
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
329
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
330
|
+
is_compile_time_only=False,
|
|
331
|
+
to_cpp="uint32_t", to_cpp_param_type="uint32_t",
|
|
332
|
+
element_qname=None, int_bits=32, int_signed=False),
|
|
333
|
+
"tpy.UInt64": dict(category=TypeCategory.FIXED_INT, is_value_type=True,
|
|
334
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
335
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
336
|
+
is_compile_time_only=False,
|
|
337
|
+
to_cpp="uint64_t", to_cpp_param_type="uint64_t",
|
|
338
|
+
element_qname=None, int_bits=64, int_signed=False),
|
|
339
|
+
|
|
340
|
+
# --- Big int ---------------------------------------------------------
|
|
341
|
+
"builtins.int": dict(category=TypeCategory.BIG_INT, is_value_type=True,
|
|
342
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
343
|
+
is_expensive_copy=True, param_needs_copy_for_reassign=True,
|
|
344
|
+
is_compile_time_only=False,
|
|
345
|
+
to_cpp="::tpy::BigInt",
|
|
346
|
+
to_cpp_param_type="const ::tpy::BigInt&",
|
|
347
|
+
element_qname=None),
|
|
348
|
+
|
|
349
|
+
# --- Floats ----------------------------------------------------------
|
|
350
|
+
"builtins.float": dict(category=TypeCategory.FLOAT, is_value_type=True,
|
|
351
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
352
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
353
|
+
is_compile_time_only=False,
|
|
354
|
+
to_cpp="double", to_cpp_param_type="double",
|
|
355
|
+
element_qname=None, float_bits=64),
|
|
356
|
+
"tpy.Float32": dict(category=TypeCategory.FLOAT, is_value_type=True,
|
|
357
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
358
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
359
|
+
is_compile_time_only=False,
|
|
360
|
+
to_cpp="float", to_cpp_param_type="float",
|
|
361
|
+
element_qname=None, float_bits=32),
|
|
362
|
+
|
|
363
|
+
# --- Bool / Char -----------------------------------------------------
|
|
364
|
+
"builtins.bool": dict(category=TypeCategory.BOOL, is_value_type=True,
|
|
365
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
366
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
367
|
+
is_compile_time_only=False,
|
|
368
|
+
to_cpp="bool", to_cpp_param_type="bool",
|
|
369
|
+
element_qname=None),
|
|
370
|
+
"tpy.Char": dict(category=TypeCategory.CHAR, is_value_type=True,
|
|
371
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
372
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
373
|
+
is_compile_time_only=False,
|
|
374
|
+
to_cpp="char", to_cpp_param_type="char",
|
|
375
|
+
element_qname=None),
|
|
376
|
+
|
|
377
|
+
# --- String family ---------------------------------------------------
|
|
378
|
+
"builtins.str": dict(category=TypeCategory.STR, is_value_type=True,
|
|
379
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
380
|
+
is_expensive_copy=True, param_needs_copy_for_reassign=True,
|
|
381
|
+
is_compile_time_only=False,
|
|
382
|
+
to_cpp="std::string",
|
|
383
|
+
to_cpp_param_type="std::string_view",
|
|
384
|
+
element_qname="tpy.Char"),
|
|
385
|
+
"tpy.String": dict(category=TypeCategory.STR, is_value_type=True,
|
|
386
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
387
|
+
is_expensive_copy=True, param_needs_copy_for_reassign=True,
|
|
388
|
+
is_compile_time_only=False,
|
|
389
|
+
to_cpp="std::string",
|
|
390
|
+
to_cpp_param_type="const std::string&",
|
|
391
|
+
element_qname="tpy.Char"),
|
|
392
|
+
"tpy.StrView": dict(category=TypeCategory.STR, is_value_type=True,
|
|
393
|
+
is_send=False, is_sync=True, subscript_borrows=False,
|
|
394
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
395
|
+
is_compile_time_only=False,
|
|
396
|
+
to_cpp="std::string_view",
|
|
397
|
+
to_cpp_param_type="std::string_view",
|
|
398
|
+
element_qname="tpy.Char"),
|
|
399
|
+
"tpy.FStr": dict(category=TypeCategory.STR, is_value_type=True,
|
|
400
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
401
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
402
|
+
is_compile_time_only=True,
|
|
403
|
+
to_cpp=_RAISES_TYPE_ERROR, to_cpp_param_type=_RAISES_TYPE_ERROR,
|
|
404
|
+
element_qname=None),
|
|
405
|
+
|
|
406
|
+
# --- Bytes family ----------------------------------------------------
|
|
407
|
+
"builtins.bytes": dict(category=TypeCategory.BYTES, is_value_type=True,
|
|
408
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
409
|
+
is_expensive_copy=True, param_needs_copy_for_reassign=True,
|
|
410
|
+
is_compile_time_only=False,
|
|
411
|
+
to_cpp="std::vector<uint8_t>",
|
|
412
|
+
to_cpp_param_type="std::span<const uint8_t>",
|
|
413
|
+
element_qname="tpy.UInt8"),
|
|
414
|
+
"builtins.bytearray": dict(category=TypeCategory.BYTES, is_value_type=True,
|
|
415
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
416
|
+
is_expensive_copy=True, param_needs_copy_for_reassign=True,
|
|
417
|
+
is_compile_time_only=False,
|
|
418
|
+
to_cpp="std::vector<uint8_t>",
|
|
419
|
+
to_cpp_param_type="const std::vector<uint8_t>&",
|
|
420
|
+
element_qname="tpy.UInt8"),
|
|
421
|
+
"tpy.BytesView": dict(category=TypeCategory.BYTES, is_value_type=True,
|
|
422
|
+
is_send=False, is_sync=True, subscript_borrows=False,
|
|
423
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
424
|
+
is_compile_time_only=False,
|
|
425
|
+
to_cpp="std::span<const uint8_t>",
|
|
426
|
+
to_cpp_param_type="std::span<const uint8_t>",
|
|
427
|
+
element_qname="tpy.UInt8"),
|
|
428
|
+
|
|
429
|
+
# --- Slices ----------------------------------------------------------
|
|
430
|
+
"tpy.basic_slice": dict(category=TypeCategory.SLICE, is_value_type=True,
|
|
431
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
432
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
433
|
+
is_compile_time_only=False,
|
|
434
|
+
to_cpp="::tpy::BasicSlice",
|
|
435
|
+
to_cpp_param_type="::tpy::BasicSlice",
|
|
436
|
+
element_qname=None),
|
|
437
|
+
"builtins.slice": dict(category=TypeCategory.SLICE, is_value_type=True,
|
|
438
|
+
is_send=True, is_sync=True, subscript_borrows=False,
|
|
439
|
+
is_expensive_copy=False, param_needs_copy_for_reassign=False,
|
|
440
|
+
is_compile_time_only=False,
|
|
441
|
+
to_cpp="::tpy::Slice",
|
|
442
|
+
to_cpp_param_type="::tpy::Slice",
|
|
443
|
+
element_qname=None),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
_PRIMITIVE_INSTANCES: dict[str, ts.TpyType] = {
|
|
448
|
+
"tpy.Int8": ts.INT8, "tpy.Int16": ts.INT16,
|
|
449
|
+
"tpy.Int32": ts.INT32, "tpy.Int64": ts.INT64,
|
|
450
|
+
"tpy.UInt8": ts.UINT8, "tpy.UInt16": ts.UINT16,
|
|
451
|
+
"tpy.UInt32": ts.UINT32, "tpy.UInt64": ts.UINT64,
|
|
452
|
+
"builtins.int": ts.BIGINT,
|
|
453
|
+
"builtins.float": ts.FLOAT,
|
|
454
|
+
"tpy.Float32": ts.FLOAT32,
|
|
455
|
+
"builtins.bool": ts.BOOL,
|
|
456
|
+
"tpy.Char": ts.CHAR,
|
|
457
|
+
"builtins.str": ts.STR,
|
|
458
|
+
"tpy.String": ts.STRING,
|
|
459
|
+
"tpy.StrView": ts.STRVIEW,
|
|
460
|
+
"tpy.FStr": ts.FSTR,
|
|
461
|
+
"builtins.bytes": ts.BYTES,
|
|
462
|
+
"builtins.bytearray": ts.BYTEARRAY,
|
|
463
|
+
"tpy.BytesView": ts.BYTESVIEW,
|
|
464
|
+
"tpy.basic_slice": ts.BASIC_SLICE,
|
|
465
|
+
"builtins.slice": ts.SLICE,
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def test_primitive_snapshot_covers_every_registered_primitive():
|
|
470
|
+
"""Every registry entry in a primitive-ish category must appear in the
|
|
471
|
+
snapshot. If a new primitive qname gets registered without being added
|
|
472
|
+
here, this test fails and the author must decide whether the new qname
|
|
473
|
+
is primitive (add to snapshot) or not (expand this ignore list)."""
|
|
474
|
+
primitive_categories = {
|
|
475
|
+
TypeCategory.FIXED_INT, TypeCategory.BIG_INT, TypeCategory.FLOAT,
|
|
476
|
+
TypeCategory.BOOL, TypeCategory.CHAR, TypeCategory.STR,
|
|
477
|
+
TypeCategory.BYTES, TypeCategory.SLICE,
|
|
478
|
+
}
|
|
479
|
+
registered_primitives = {
|
|
480
|
+
qn for qn, td in _type_defs.items() if td.category in primitive_categories
|
|
481
|
+
}
|
|
482
|
+
missing = registered_primitives - set(PRIMITIVE_SNAPSHOT)
|
|
483
|
+
assert not missing, (
|
|
484
|
+
f"Registered primitive qnames without a snapshot entry: {missing}. "
|
|
485
|
+
f"Add them to PRIMITIVE_SNAPSHOT with their expected per-qname behavior."
|
|
486
|
+
)
|
|
487
|
+
extra = set(PRIMITIVE_SNAPSHOT) - registered_primitives
|
|
488
|
+
assert not extra, (
|
|
489
|
+
f"PRIMITIVE_SNAPSHOT entries that aren't registered primitives: {extra}."
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@pytest.mark.parametrize("qname", sorted(PRIMITIVE_SNAPSHOT))
|
|
494
|
+
def test_primitive_instance_matches_snapshot(qname):
|
|
495
|
+
"""Hard-coded golden snapshot of every primitive's intrinsic behavior.
|
|
496
|
+
Validated against the live TpyType instance. Must keep passing through
|
|
497
|
+
Phase D even as the primitive subclasses collapse into NominalType."""
|
|
498
|
+
expected = PRIMITIVE_SNAPSHOT[qname]
|
|
499
|
+
inst = _PRIMITIVE_INSTANCES[qname]
|
|
500
|
+
|
|
501
|
+
# qualified_name must round-trip.
|
|
502
|
+
assert inst.qualified_name() == qname
|
|
503
|
+
|
|
504
|
+
# category comes from the TypeDef registry (by construction today,
|
|
505
|
+
# still true after migration). Included for completeness -- if the
|
|
506
|
+
# category ever drifts this catches it.
|
|
507
|
+
td = get_type_def(qname)
|
|
508
|
+
assert td is not None
|
|
509
|
+
assert td.category is expected["category"], (
|
|
510
|
+
f"{qname}: TypeDef.category={td.category!r} "
|
|
511
|
+
f"but snapshot expects {expected['category']!r}"
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
assert inst.is_value_type() == expected["is_value_type"], qname
|
|
515
|
+
assert inst.is_send() == expected["is_send"], qname
|
|
516
|
+
assert inst.is_sync() == expected["is_sync"], qname
|
|
517
|
+
assert inst.subscript_borrows() == expected["subscript_borrows"], qname
|
|
518
|
+
assert inst.is_expensive_copy() == expected["is_expensive_copy"], qname
|
|
519
|
+
assert inst.param_needs_copy_for_reassign() == expected["param_needs_copy_for_reassign"], qname
|
|
520
|
+
assert inst.is_compile_time_only() == expected["is_compile_time_only"], qname
|
|
521
|
+
|
|
522
|
+
if expected["to_cpp"] is _RAISES_TYPE_ERROR:
|
|
523
|
+
with pytest.raises(TypeError):
|
|
524
|
+
inst.to_cpp()
|
|
525
|
+
else:
|
|
526
|
+
assert inst.to_cpp() == expected["to_cpp"], qname
|
|
527
|
+
|
|
528
|
+
if expected["to_cpp_param_type"] is _RAISES_TYPE_ERROR:
|
|
529
|
+
with pytest.raises(TypeError):
|
|
530
|
+
inst.to_cpp_param_type()
|
|
531
|
+
else:
|
|
532
|
+
assert inst.to_cpp_param_type() == expected["to_cpp_param_type"], qname
|
|
533
|
+
|
|
534
|
+
elem = inst.get_element_type()
|
|
535
|
+
if expected["element_qname"] is None:
|
|
536
|
+
assert elem is None, f"{qname}: expected no element, got {elem!r}"
|
|
537
|
+
else:
|
|
538
|
+
assert elem is not None, f"{qname}: expected element, got None"
|
|
539
|
+
assert elem.qualified_name() == expected["element_qname"], qname
|
|
540
|
+
|
|
541
|
+
# Category-specific checks.
|
|
542
|
+
if expected["category"] is TypeCategory.FIXED_INT:
|
|
543
|
+
# Post-Phase-D: bits/signed live in TypeDef.int_traits, reached via
|
|
544
|
+
# int_traits_of(inst). Pin the accessor so Phase D can't silently
|
|
545
|
+
# break the lookup for any caller.
|
|
546
|
+
from tpyc.type_def_registry import int_traits_of
|
|
547
|
+
tr = int_traits_of(inst)
|
|
548
|
+
assert tr is not None, qname
|
|
549
|
+
assert tr.bits == expected["int_bits"], qname
|
|
550
|
+
assert tr.signed == expected["int_signed"], qname
|
|
551
|
+
|
|
552
|
+
|
|
553
|
+
@pytest.mark.parametrize("qname", sorted(PRIMITIVE_SNAPSHOT))
|
|
554
|
+
def test_primitive_type_def_matches_snapshot(qname):
|
|
555
|
+
"""The TypeDef registry entry for each primitive qname must carry the
|
|
556
|
+
snapshot values directly (independent of any TpyType subclass). This
|
|
557
|
+
is the forward-facing half of Phase D validation: Step 1 enriches
|
|
558
|
+
TypeDef with these fields; later steps flip callers to read them. The
|
|
559
|
+
subclass check above and this check both compare against the same
|
|
560
|
+
PRIMITIVE_SNAPSHOT, so if a caller reads TypeDef and drifts from
|
|
561
|
+
observed behavior the pair of tests catches it."""
|
|
562
|
+
expected = PRIMITIVE_SNAPSHOT[qname]
|
|
563
|
+
td = get_type_def(qname)
|
|
564
|
+
assert td is not None, qname
|
|
565
|
+
assert td.category is expected["category"], qname
|
|
566
|
+
assert td.is_value_type == expected["is_value_type"], qname
|
|
567
|
+
assert td.subscript_borrows == expected["subscript_borrows"], qname
|
|
568
|
+
assert td.is_expensive_copy == expected["is_expensive_copy"], qname
|
|
569
|
+
assert td.param_needs_copy_for_reassign == expected["param_needs_copy_for_reassign"], qname
|
|
570
|
+
assert td.is_compile_time_only == expected["is_compile_time_only"], qname
|
|
571
|
+
|
|
572
|
+
# is_send / is_sync: default None means "follow is_value_type". Resolve
|
|
573
|
+
# against empty type_args (primitives have none) and compare.
|
|
574
|
+
send_resolved = resolve_send_sync(td.is_send, ())
|
|
575
|
+
expected_send = expected["is_send"]
|
|
576
|
+
if send_resolved is None:
|
|
577
|
+
assert td.is_value_type == expected_send, qname
|
|
578
|
+
else:
|
|
579
|
+
assert send_resolved == expected_send, qname
|
|
580
|
+
sync_resolved = resolve_send_sync(td.is_sync, ())
|
|
581
|
+
expected_sync = expected["is_sync"]
|
|
582
|
+
if sync_resolved is None:
|
|
583
|
+
assert td.is_value_type == expected_sync, qname
|
|
584
|
+
else:
|
|
585
|
+
assert sync_resolved == expected_sync, qname
|
|
586
|
+
|
|
587
|
+
# cpp_formatter: populated for every primitive except FStr (compile-
|
|
588
|
+
# time only -- its to_cpp raises TypeError, which is an absence of
|
|
589
|
+
# formatter rather than a concrete string).
|
|
590
|
+
if expected["to_cpp"] is _RAISES_TYPE_ERROR:
|
|
591
|
+
assert td.cpp_formatter is None, qname
|
|
592
|
+
else:
|
|
593
|
+
assert td.cpp_formatter is not None, qname
|
|
594
|
+
assert td.cpp_formatter(()) == expected["to_cpp"], qname
|
|
595
|
+
|
|
596
|
+
if expected["to_cpp_param_type"] is _RAISES_TYPE_ERROR:
|
|
597
|
+
assert td.param_cpp_formatter is None, qname
|
|
598
|
+
else:
|
|
599
|
+
assert td.param_cpp_formatter is not None, qname
|
|
600
|
+
assert td.param_cpp_formatter(()) == expected["to_cpp_param_type"], qname
|
|
601
|
+
|
|
602
|
+
if expected["category"] is TypeCategory.FIXED_INT:
|
|
603
|
+
assert td.int_traits is not None, qname
|
|
604
|
+
assert td.int_traits.bits == expected["int_bits"], qname
|
|
605
|
+
assert td.int_traits.signed == expected["int_signed"], qname
|
|
606
|
+
else:
|
|
607
|
+
assert td.int_traits is None, qname
|
|
608
|
+
|
|
609
|
+
if expected["category"] is TypeCategory.FLOAT:
|
|
610
|
+
assert td.float_traits is not None, qname
|
|
611
|
+
assert td.float_traits.bits == expected["float_bits"], qname
|
|
612
|
+
else:
|
|
613
|
+
assert td.float_traits is None, qname
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def test_primitive_predicates_match_isinstance():
|
|
617
|
+
"""Every primitive predicate returns True for exactly one canonical qname
|
|
618
|
+
(or, for category predicates like is_fixed_int_type, for exactly the
|
|
619
|
+
expected set of qnames). Post-Phase D the primitive subclasses are gone,
|
|
620
|
+
so the historical isinstance arm of this test is empty; the qname-based
|
|
621
|
+
checks below are what pins predicate correctness today."""
|
|
622
|
+
from tpyc.type_def_registry import (
|
|
623
|
+
is_fixed_int_type, is_big_int_type, is_bool_type, is_char_type,
|
|
624
|
+
is_float_category, is_str_category, is_bytes_category, is_slice_category,
|
|
625
|
+
is_str_type, is_string_type, is_str_view_type, is_fstr_type,
|
|
626
|
+
is_float64_type, is_float32_type,
|
|
627
|
+
is_bytes_type, is_bytearray_type, is_bytes_view_type,
|
|
628
|
+
)
|
|
629
|
+
# After Phase D step 6: all primitive subclasses are NominalType singletons.
|
|
630
|
+
# Validate the qname-only predicates directly (no isinstance-equivalent).
|
|
631
|
+
qname_preds = {
|
|
632
|
+
is_fstr_type: "tpy.FStr",
|
|
633
|
+
is_str_type: "builtins.str",
|
|
634
|
+
is_string_type: "tpy.String",
|
|
635
|
+
is_str_view_type: "tpy.StrView",
|
|
636
|
+
is_bytes_type: "builtins.bytes",
|
|
637
|
+
is_bytearray_type: "builtins.bytearray",
|
|
638
|
+
is_bytes_view_type: "tpy.BytesView",
|
|
639
|
+
is_bool_type: "builtins.bool",
|
|
640
|
+
is_char_type: "tpy.Char",
|
|
641
|
+
is_float64_type: "builtins.float",
|
|
642
|
+
is_float32_type: "tpy.Float32",
|
|
643
|
+
is_big_int_type: "builtins.int",
|
|
644
|
+
}
|
|
645
|
+
# Fixed-int qnames span Int8..UInt64; is_fixed_int_type is a category
|
|
646
|
+
# predicate, so check it membership-style instead of qname-equality.
|
|
647
|
+
fixed_int_qnames = {
|
|
648
|
+
"tpy.Int8", "tpy.Int16", "tpy.Int32", "tpy.Int64",
|
|
649
|
+
"tpy.UInt8", "tpy.UInt16", "tpy.UInt32", "tpy.UInt64",
|
|
650
|
+
}
|
|
651
|
+
for qname, inst in _PRIMITIVE_INSTANCES.items():
|
|
652
|
+
assert is_fixed_int_type(inst) == (qname in fixed_int_qnames), (
|
|
653
|
+
f"is_fixed_int_type({qname}) = {is_fixed_int_type(inst)}"
|
|
654
|
+
)
|
|
655
|
+
for pred, expected_qname in qname_preds.items():
|
|
656
|
+
for qname, inst in _PRIMITIVE_INSTANCES.items():
|
|
657
|
+
assert pred(inst) == (qname == expected_qname), (
|
|
658
|
+
f"{pred.__name__}({qname}) = {pred(inst)}"
|
|
659
|
+
)
|
|
660
|
+
|
|
661
|
+
# Category predicates: verify against snapshot category.
|
|
662
|
+
cat_preds = {
|
|
663
|
+
TypeCategory.FIXED_INT: is_fixed_int_type,
|
|
664
|
+
TypeCategory.BIG_INT: is_big_int_type,
|
|
665
|
+
TypeCategory.BOOL: is_bool_type,
|
|
666
|
+
TypeCategory.CHAR: is_char_type,
|
|
667
|
+
TypeCategory.FLOAT: is_float_category,
|
|
668
|
+
TypeCategory.STR: is_str_category,
|
|
669
|
+
TypeCategory.BYTES: is_bytes_category,
|
|
670
|
+
TypeCategory.SLICE: is_slice_category,
|
|
671
|
+
}
|
|
672
|
+
for qname, expected in PRIMITIVE_SNAPSHOT.items():
|
|
673
|
+
inst = _PRIMITIVE_INSTANCES[qname]
|
|
674
|
+
for cat, pred in cat_preds.items():
|
|
675
|
+
assert pred(inst) == (expected["category"] is cat), (
|
|
676
|
+
f"{pred.__name__}({qname}) = {pred(inst)} but "
|
|
677
|
+
f"category is {expected['category']}"
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def test_trait_accessors_match_snapshot():
|
|
682
|
+
"""int_traits_of / float_traits_of must return the expected traits for
|
|
683
|
+
category-appropriate primitives, None otherwise."""
|
|
684
|
+
from tpyc.type_def_registry import int_traits_of, float_traits_of
|
|
685
|
+
for qname, expected in PRIMITIVE_SNAPSHOT.items():
|
|
686
|
+
inst = _PRIMITIVE_INSTANCES[qname]
|
|
687
|
+
int_tr = int_traits_of(inst)
|
|
688
|
+
float_tr = float_traits_of(inst)
|
|
689
|
+
if expected["category"] is TypeCategory.FIXED_INT:
|
|
690
|
+
assert int_tr is not None
|
|
691
|
+
assert int_tr.bits == expected["int_bits"], qname
|
|
692
|
+
assert int_tr.signed == expected["int_signed"], qname
|
|
693
|
+
else:
|
|
694
|
+
assert int_tr is None, qname
|
|
695
|
+
if expected["category"] is TypeCategory.FLOAT:
|
|
696
|
+
assert float_tr is not None
|
|
697
|
+
assert float_tr.bits == expected["float_bits"], qname
|
|
698
|
+
else:
|
|
699
|
+
assert float_tr is None, qname
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
def test_is_any_str_type_excludes_fstr():
|
|
703
|
+
"""is_any_str_type must not report FStr as a string type even though FStr
|
|
704
|
+
shares TypeCategory.STR with the runtime str family in the registry.
|
|
705
|
+
FStr is compile-time only and is never a valid str value at runtime --
|
|
706
|
+
callers relying on is_any_str_type to branch on runtime str types would
|
|
707
|
+
misbehave if FStr slipped in."""
|
|
708
|
+
assert not ts.is_any_str_type(ts.FSTR)
|
|
709
|
+
# Sanity: the legitimate str-family types still report True.
|
|
710
|
+
assert ts.is_any_str_type(ts.STR)
|
|
711
|
+
assert ts.is_any_str_type(ts.STRING)
|
|
712
|
+
assert ts.is_any_str_type(ts.STRVIEW)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
# =========================================================================
|
|
716
|
+
# Enum hard-coded snapshot (Phase E).
|
|
717
|
+
#
|
|
718
|
+
# EnumType and IntEnumType are the last nominal-type subclasses. Before
|
|
719
|
+
# collapsing them into NominalType + TypeDef.enum, pin their intrinsic
|
|
720
|
+
# behavior so the migration path has an independent reference: this
|
|
721
|
+
# snapshot is hand-written expected values, not derived from the subclass
|
|
722
|
+
# methods, and must keep passing through every migration step (add
|
|
723
|
+
# TypeDef.enum -> populate on registration -> flip readers -> delete
|
|
724
|
+
# subclass). Mirrors PRIMITIVE_SNAPSHOT's role for Phase D.
|
|
725
|
+
# =========================================================================
|
|
726
|
+
|
|
727
|
+
|
|
728
|
+
def _build_enum_snapshot_instances() -> dict[str, "ts.NominalType"]:
|
|
729
|
+
"""Build sample enum instances for the snapshot.
|
|
730
|
+
|
|
731
|
+
Mirrors what sema/registration.py does: each enum is a NominalType with
|
|
732
|
+
`_module_qname` set (so `type_def_of(t)` resolves), and its EnumInfo
|
|
733
|
+
payload is attached to the TypeDef registry so `enum_info_of(t)`
|
|
734
|
+
returns the members / underlying_type / is_int_enum data. Covers:
|
|
735
|
+
qualified + unqualified (mimicking `__main__`) plain enums, and
|
|
736
|
+
IntEnum variants with default Int32 and non-default UInt8 underlyings.
|
|
737
|
+
"""
|
|
738
|
+
from tpyc.type_def_registry import (
|
|
739
|
+
attach_dynamic_type_def, TypeCategory, EnumInfo as _EnumInfo,
|
|
740
|
+
)
|
|
741
|
+
fixtures = [
|
|
742
|
+
# (short name, module_name for qname, module_name on EnumInfo, is_int_enum, members, underlying)
|
|
743
|
+
("Color", "palette", "palette", False,
|
|
744
|
+
(("Red", 0), ("Green", 1), ("Blue", 2)), ts.INT32),
|
|
745
|
+
# module_name=None on the EnumInfo (matches __main__ enum), but the
|
|
746
|
+
# qname uses "__main__" as prefix so TypeDef lookup still works.
|
|
747
|
+
("Status", "__main__", None, False,
|
|
748
|
+
(("Ok", 0), ("Err", 1)), ts.INT32),
|
|
749
|
+
("Level", "log", "log", True,
|
|
750
|
+
(("Low", 0), ("High", 1)), ts.INT32),
|
|
751
|
+
("Flags", "perms", "perms", True,
|
|
752
|
+
(("Read", 1), ("Write", 2), ("Exec", 4)), ts.UINT8),
|
|
753
|
+
]
|
|
754
|
+
result: dict[str, ts.NominalType] = {}
|
|
755
|
+
for name, qname_module, info_module, is_int_enum, member_values, underlying in fixtures:
|
|
756
|
+
members = tuple(m for m, _ in member_values)
|
|
757
|
+
qname = f"{qname_module}.{name}"
|
|
758
|
+
t = ts.NominalType(name=name, type_args=(), _module_qname=qname)
|
|
759
|
+
attach_dynamic_type_def(
|
|
760
|
+
qname, TypeCategory.ENUM,
|
|
761
|
+
enum=_EnumInfo(
|
|
762
|
+
members=members,
|
|
763
|
+
member_values=member_values,
|
|
764
|
+
underlying_type=underlying,
|
|
765
|
+
is_int_enum=is_int_enum,
|
|
766
|
+
module_name=info_module,
|
|
767
|
+
),
|
|
768
|
+
is_value_type=True,
|
|
769
|
+
)
|
|
770
|
+
result[name] = t
|
|
771
|
+
return result
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
# Hand-written expected values -- do NOT derive from the instance.
|
|
775
|
+
# The point of the snapshot is that these answers survive the migration
|
|
776
|
+
# from subclass fields to TypeDef.enum.
|
|
777
|
+
ENUM_SNAPSHOT: dict[str, dict] = {
|
|
778
|
+
"Color": dict(
|
|
779
|
+
is_int_enum=False,
|
|
780
|
+
members=("Red", "Green", "Blue"),
|
|
781
|
+
member_values=(("Red", 0), ("Green", 1), ("Blue", 2)),
|
|
782
|
+
underlying_qname="tpy.Int32",
|
|
783
|
+
qualified_name="palette.Color",
|
|
784
|
+
info_module_name="palette",
|
|
785
|
+
to_cpp="Color",
|
|
786
|
+
is_value_type=True, is_send=True, is_sync=True,
|
|
787
|
+
subscript_borrows=False, is_expensive_copy=False,
|
|
788
|
+
),
|
|
789
|
+
"Status": dict(
|
|
790
|
+
is_int_enum=False,
|
|
791
|
+
members=("Ok", "Err"),
|
|
792
|
+
member_values=(("Ok", 0), ("Err", 1)),
|
|
793
|
+
underlying_qname="tpy.Int32",
|
|
794
|
+
qualified_name="__main__.Status",
|
|
795
|
+
info_module_name=None, # __main__ enums preserve None on EnumInfo.module_name
|
|
796
|
+
to_cpp="Status",
|
|
797
|
+
is_value_type=True, is_send=True, is_sync=True,
|
|
798
|
+
subscript_borrows=False, is_expensive_copy=False,
|
|
799
|
+
),
|
|
800
|
+
"Level": dict(
|
|
801
|
+
is_int_enum=True,
|
|
802
|
+
members=("Low", "High"),
|
|
803
|
+
member_values=(("Low", 0), ("High", 1)),
|
|
804
|
+
underlying_qname="tpy.Int32",
|
|
805
|
+
qualified_name="log.Level",
|
|
806
|
+
info_module_name="log",
|
|
807
|
+
to_cpp="Level",
|
|
808
|
+
is_value_type=True, is_send=True, is_sync=True,
|
|
809
|
+
subscript_borrows=False, is_expensive_copy=False,
|
|
810
|
+
),
|
|
811
|
+
"Flags": dict(
|
|
812
|
+
is_int_enum=True,
|
|
813
|
+
members=("Read", "Write", "Exec"),
|
|
814
|
+
member_values=(("Read", 1), ("Write", 2), ("Exec", 4)),
|
|
815
|
+
underlying_qname="tpy.UInt8",
|
|
816
|
+
qualified_name="perms.Flags",
|
|
817
|
+
info_module_name="perms",
|
|
818
|
+
to_cpp="Flags",
|
|
819
|
+
is_value_type=True, is_send=True, is_sync=True,
|
|
820
|
+
subscript_borrows=False, is_expensive_copy=False,
|
|
821
|
+
),
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
@pytest.mark.parametrize("name", sorted(ENUM_SNAPSHOT))
|
|
826
|
+
def test_enum_instance_matches_snapshot(name):
|
|
827
|
+
"""Pin enum behavior against hand-coded expected answers. Post-Phase-E
|
|
828
|
+
enums are NominalType + TypeDef.enum; accessor data is read via
|
|
829
|
+
`enum_info_of(t)`."""
|
|
830
|
+
from tpyc.type_def_registry import (
|
|
831
|
+
enum_info_of, is_enum_type, is_int_enum_type,
|
|
832
|
+
)
|
|
833
|
+
expected = ENUM_SNAPSHOT[name]
|
|
834
|
+
instances = _build_enum_snapshot_instances()
|
|
835
|
+
inst = instances[name]
|
|
836
|
+
info = enum_info_of(inst)
|
|
837
|
+
assert info is not None, f"enum_info_of({name}) returned None"
|
|
838
|
+
|
|
839
|
+
# Structural identity.
|
|
840
|
+
assert inst.name == name
|
|
841
|
+
assert info.members == expected["members"]
|
|
842
|
+
assert info.member_values == expected["member_values"]
|
|
843
|
+
assert info.underlying_type.qualified_name() == expected["underlying_qname"]
|
|
844
|
+
assert inst.qualified_name() == expected["qualified_name"]
|
|
845
|
+
assert info.module_name == expected["info_module_name"]
|
|
846
|
+
|
|
847
|
+
# IntEnum is distinguished by the is_int_enum flag on EnumInfo.
|
|
848
|
+
assert info.is_int_enum == expected["is_int_enum"]
|
|
849
|
+
assert is_int_enum_type(inst) == expected["is_int_enum"]
|
|
850
|
+
assert is_enum_type(inst)
|
|
851
|
+
|
|
852
|
+
# Behavior (inherited from NominalType, resolved via TypeDef).
|
|
853
|
+
assert inst.is_value_type() == expected["is_value_type"]
|
|
854
|
+
assert inst.is_send() == expected["is_send"]
|
|
855
|
+
assert inst.is_sync() == expected["is_sync"]
|
|
856
|
+
assert inst.subscript_borrows() == expected["subscript_borrows"]
|
|
857
|
+
assert inst.is_expensive_copy() == expected["is_expensive_copy"]
|
|
858
|
+
assert inst.to_cpp() == expected["to_cpp"]
|
|
859
|
+
|
|
860
|
+
# member_value_map should round-trip from member_values.
|
|
861
|
+
assert info.member_value_map == dict(expected["member_values"])
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def test_enum_predicates_reject_non_enum_types():
|
|
865
|
+
"""enum_info_of / is_enum_type / is_int_enum_type must return None / False
|
|
866
|
+
(not crash) on non-enum inputs. Covers the common cases: primitives,
|
|
867
|
+
containers, structural wrappers, and the tricky LiteralType / PendingView
|
|
868
|
+
siblings whose qualified_name delegates to a base type's qname."""
|
|
869
|
+
from tpyc.type_def_registry import enum_info_of, is_enum_type, is_int_enum_type
|
|
870
|
+
non_enum_cases = [
|
|
871
|
+
ts.INT32, ts.BIGINT, ts.BOOL, ts.STR, ts.FLOAT,
|
|
872
|
+
ts.make_list(ts.INT32),
|
|
873
|
+
ts.make_dict(ts.STR, ts.INT32),
|
|
874
|
+
ts.make_range(ts.INT32),
|
|
875
|
+
ts.OptionalType(ts.INT32),
|
|
876
|
+
ts.TupleType((ts.INT32, ts.STR)),
|
|
877
|
+
]
|
|
878
|
+
for t in non_enum_cases:
|
|
879
|
+
assert enum_info_of(t) is None, f"enum_info_of({t}) should be None"
|
|
880
|
+
assert not is_enum_type(t), f"is_enum_type({t}) should be False"
|
|
881
|
+
assert not is_int_enum_type(t), f"is_int_enum_type({t}) should be False"
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def test_fixed_int_range_bounds_match_width():
|
|
885
|
+
"""min_value / max_value are derived from (bits, signed). Pin the
|
|
886
|
+
formula against a few representative widths so Phase D can't silently
|
|
887
|
+
change the range computation."""
|
|
888
|
+
cases = [
|
|
889
|
+
("tpy.Int8", -2**7, 2**7 - 1),
|
|
890
|
+
("tpy.Int16", -2**15, 2**15 - 1),
|
|
891
|
+
("tpy.Int32", -2**31, 2**31 - 1),
|
|
892
|
+
("tpy.Int64", -2**63, 2**63 - 1),
|
|
893
|
+
("tpy.UInt8", 0, 2**8 - 1),
|
|
894
|
+
("tpy.UInt16", 0, 2**16 - 1),
|
|
895
|
+
("tpy.UInt32", 0, 2**32 - 1),
|
|
896
|
+
("tpy.UInt64", 0, 2**64 - 1),
|
|
897
|
+
]
|
|
898
|
+
for qname, lo, hi in cases:
|
|
899
|
+
td = get_type_def(qname)
|
|
900
|
+
assert td is not None and td.int_traits is not None
|
|
901
|
+
assert td.int_traits.min_value == lo, f"{qname}"
|
|
902
|
+
assert td.int_traits.max_value == hi, f"{qname}"
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
def test_type_matches_numeric_rejects_cross_container():
|
|
906
|
+
"""Post-Phase-D all builtin containers (list, set, dict, Array, Span, ...)
|
|
907
|
+
share the NominalType class, so `type(arg) == type(param)` is True for any
|
|
908
|
+
pair of containers. type_matches_numeric must also compare the container
|
|
909
|
+
name -- otherwise resolve_overload could pick a set[Int32] overload for a
|
|
910
|
+
list[Int32] argument via the recursive element-matching branch."""
|
|
911
|
+
from tpyc.sema.overloads import type_matches_numeric
|
|
912
|
+
assert not type_matches_numeric(ts.make_list(ts.INT32), ts.make_set(ts.INT32))
|
|
913
|
+
assert not type_matches_numeric(ts.make_set(ts.INT32), ts.make_list(ts.INT32))
|
|
914
|
+
assert not type_matches_numeric(
|
|
915
|
+
ts.make_dict(ts.STR, ts.INT32),
|
|
916
|
+
ts.make_list(ts.INT32),
|
|
917
|
+
)
|
|
918
|
+
# Positive case: same container with IntLiteral element should still match
|
|
919
|
+
# the concrete-int overload via the element-recursion branch.
|
|
920
|
+
lit = ts.IntLiteralType(value=5)
|
|
921
|
+
assert type_matches_numeric(ts.make_list(lit), ts.make_list(ts.INT32))
|
|
922
|
+
|
|
923
|
+
|
|
924
|
+
def test_structural_match_rejects_cross_container():
|
|
925
|
+
"""Same anti-pattern as type_matches_numeric but in _structural_match
|
|
926
|
+
(pass-1 overload resolution). Post-Phase-D list/set/Array/Span are all
|
|
927
|
+
NominalType, so `type(x) != type(y)` returns False and the recursive
|
|
928
|
+
inner-type match would fire -- needs a name comparison too."""
|
|
929
|
+
from tpyc.sema.overloads import _structural_match
|
|
930
|
+
assert not _structural_match(ts.make_list(ts.INT32), ts.make_set(ts.INT32))
|
|
931
|
+
assert not _structural_match(ts.make_set(ts.INT32), ts.make_list(ts.INT32))
|
|
932
|
+
# Positive case: exact match still works.
|
|
933
|
+
assert _structural_match(ts.make_list(ts.INT32), ts.make_list(ts.INT32))
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
# =========================================================================
|
|
937
|
+
# Dynamic TypeDef attachment (Phase E step 2).
|
|
938
|
+
#
|
|
939
|
+
# sema/registration.py attaches RecordInfo / ProtocolInfo onto the TypeDef
|
|
940
|
+
# registry as records and protocols come through register_record /
|
|
941
|
+
# register_protocol. The tests here pin the attach-and-clear behavior in
|
|
942
|
+
# isolation, without going through a full compile. The conftest autouse
|
|
943
|
+
# fixture calls clear_all_compilation_state() around every test, so each
|
|
944
|
+
# case starts from a clean dynamic slate.
|
|
945
|
+
# =========================================================================
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def test_attach_dynamic_creates_new_typedef():
|
|
949
|
+
"""Purely-dynamic qname (no static entry): attach creates a TypeDef."""
|
|
950
|
+
from tpyc.type_def_registry import attach_dynamic_type_def, clear_dynamic_type_defs
|
|
951
|
+
qname = "test_module.TestProtocol"
|
|
952
|
+
assert get_type_def(qname) is None
|
|
953
|
+
stub = object() # opaque ProtocolInfo stand-in
|
|
954
|
+
td = attach_dynamic_type_def(qname, TypeCategory.PROTOCOL, protocol=stub)
|
|
955
|
+
assert td is get_type_def(qname)
|
|
956
|
+
assert td.category is TypeCategory.PROTOCOL
|
|
957
|
+
assert td.protocol is stub
|
|
958
|
+
assert td.record is None
|
|
959
|
+
clear_dynamic_type_defs()
|
|
960
|
+
assert get_type_def(qname) is None
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def test_record_and_protocol_attach_during_compile():
|
|
964
|
+
"""End-to-end: compile a module with a user protocol and verify that the
|
|
965
|
+
TypeDef registry has the ProtocolInfo attached under its qname. The
|
|
966
|
+
conformance tests alone don't hit the sema hook -- this pins that the
|
|
967
|
+
hook actually fires for real compiles. Looks up the TypeDef by qname
|
|
968
|
+
directly so the assertion doesn't depend on NominalType.qualified_name()
|
|
969
|
+
walking the `_protocol_modules` side-channel.
|
|
970
|
+
|
|
971
|
+
Note: `Compiler.from_source` compiles the source as the entry point, so
|
|
972
|
+
its runtime module identity is `__main__` regardless of the `module_name`
|
|
973
|
+
argument (which only names the generated file). Protocol registration
|
|
974
|
+
uses the runtime module, so the qname falls into the `__main__.<Name>`
|
|
975
|
+
branch of register_protocol."""
|
|
976
|
+
from tpyc import get_lib_dir
|
|
977
|
+
from tpyc.compiler import Compiler
|
|
978
|
+
source = (
|
|
979
|
+
"from typing import Protocol\n"
|
|
980
|
+
"class Greeter(Protocol):\n"
|
|
981
|
+
" def greet(self) -> str: ...\n"
|
|
982
|
+
)
|
|
983
|
+
compiler = Compiler.from_source(source,
|
|
984
|
+
lib_dirs=[get_lib_dir() / "tpy"])
|
|
985
|
+
compiler.compile()
|
|
986
|
+
# Keep the compilation's dynamic_type_defs slice visible while we
|
|
987
|
+
# inspect the registry post-compile.
|
|
988
|
+
with activate_compiler(compiler):
|
|
989
|
+
td = get_type_def("__main__.Greeter")
|
|
990
|
+
assert td is not None, (
|
|
991
|
+
"TypeDef for '__main__.Greeter' should exist after sema registers the protocol."
|
|
992
|
+
)
|
|
993
|
+
assert td.category is TypeCategory.PROTOCOL
|
|
994
|
+
assert td.protocol is not None, "TypeDef.protocol payload should be attached"
|
|
995
|
+
assert td.protocol.name == "Greeter"
|
|
996
|
+
assert any(m.name == "greet" for m in td.protocol.methods)
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
def test_attach_dynamic_updates_existing_typedef():
|
|
1000
|
+
"""Pre-existing static qname (e.g. builtins.list): attach updates only the
|
|
1001
|
+
record/protocol payload; category and cpp_formatter are preserved."""
|
|
1002
|
+
from tpyc.type_def_registry import attach_dynamic_type_def, clear_dynamic_type_defs
|
|
1003
|
+
td_before = get_type_def("builtins.list")
|
|
1004
|
+
assert td_before is not None
|
|
1005
|
+
assert td_before.category is TypeCategory.LIST
|
|
1006
|
+
original_category = td_before.category
|
|
1007
|
+
original_cpp_formatter = td_before.cpp_formatter
|
|
1008
|
+
original_record = td_before.record
|
|
1009
|
+
|
|
1010
|
+
stub = object()
|
|
1011
|
+
attach_dynamic_type_def("builtins.list", TypeCategory.RECORD, record=stub)
|
|
1012
|
+
td_after = get_type_def("builtins.list")
|
|
1013
|
+
|
|
1014
|
+
# Category and cpp_formatter survive the attachment -- only payloads change.
|
|
1015
|
+
assert td_after.category is original_category
|
|
1016
|
+
assert td_after.cpp_formatter is original_cpp_formatter
|
|
1017
|
+
assert td_after.record is stub
|
|
1018
|
+
|
|
1019
|
+
clear_dynamic_type_defs()
|
|
1020
|
+
td_cleared = get_type_def("builtins.list")
|
|
1021
|
+
# Static TypeDef remains; record payload resets to the pre-attach value.
|
|
1022
|
+
assert td_cleared is not None
|
|
1023
|
+
assert td_cleared.category is original_category
|
|
1024
|
+
assert td_cleared.record is original_record
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
# =========================================================================
|
|
1028
|
+
# Factory payload snapshot (Phase F.3e).
|
|
1029
|
+
#
|
|
1030
|
+
# Hard-coded golden table of `param_kinds` arity + kind for every factory
|
|
1031
|
+
# entry that used to live in `modules/type_resolution.py`. Pins the
|
|
1032
|
+
# contract that the merged TypeDef registry must honor. As with
|
|
1033
|
+
# PRIMITIVE_SNAPSHOT, hand-maintained -- regenerating from the registry
|
|
1034
|
+
# would defeat the check.
|
|
1035
|
+
# =========================================================================
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
def _k(kind_name: str):
|
|
1039
|
+
"""Shorthand for TypeParamKind enum members (imported lazily to avoid
|
|
1040
|
+
adding a top-level typesys import to this file)."""
|
|
1041
|
+
from tpyc.typesys import TypeParamKind
|
|
1042
|
+
return getattr(TypeParamKind, kind_name)
|
|
1043
|
+
|
|
1044
|
+
|
|
1045
|
+
# qname -> tuple of TypeParamKind strings ("TYPE" / "INT"). Empty tuple
|
|
1046
|
+
# means "no type args" (primitive singletons).
|
|
1047
|
+
FACTORY_SNAPSHOT: dict[str, tuple[str, ...]] = {
|
|
1048
|
+
# Containers
|
|
1049
|
+
"builtins.list": ("TYPE",),
|
|
1050
|
+
"builtins.dict": ("TYPE", "TYPE"),
|
|
1051
|
+
"builtins.dict_keys": ("TYPE", "TYPE"),
|
|
1052
|
+
"builtins.dict_values": ("TYPE", "TYPE"),
|
|
1053
|
+
"builtins.dict_items": ("TYPE", "TYPE"),
|
|
1054
|
+
"builtins.set": ("TYPE",),
|
|
1055
|
+
"builtins.Range": ("TYPE",),
|
|
1056
|
+
"tpy.Array": ("TYPE", "INT"),
|
|
1057
|
+
"tpy.Span": ("TYPE",),
|
|
1058
|
+
"tpy.SpanIter": ("TYPE",),
|
|
1059
|
+
"tpy.coro.Waker": (),
|
|
1060
|
+
# Structural wrapper
|
|
1061
|
+
"tpy.Ptr": ("TYPE",),
|
|
1062
|
+
# Primitive singletons
|
|
1063
|
+
"tpy.Float32": (),
|
|
1064
|
+
"tpy.Char": (),
|
|
1065
|
+
"tpy.String": (),
|
|
1066
|
+
"tpy.StrView": (),
|
|
1067
|
+
"tpy.FStr": (),
|
|
1068
|
+
"builtins.int": (),
|
|
1069
|
+
"builtins.float": (),
|
|
1070
|
+
"builtins.bool": (),
|
|
1071
|
+
"builtins.str": (),
|
|
1072
|
+
"builtins.bytes": (),
|
|
1073
|
+
"builtins.bytearray": (),
|
|
1074
|
+
"tpy.BytesView": (),
|
|
1075
|
+
"tpy.basic_slice": (),
|
|
1076
|
+
"builtins.slice": (),
|
|
1077
|
+
"tpy.Int8": (), "tpy.Int16": (), "tpy.Int32": (), "tpy.Int64": (),
|
|
1078
|
+
"tpy.UInt8": (), "tpy.UInt16": (), "tpy.UInt32": (), "tpy.UInt64": (),
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
def test_factory_snapshot_covers_every_registered_factory():
|
|
1083
|
+
"""Every TypeDef with a `type_factory` must have a FACTORY_SNAPSHOT
|
|
1084
|
+
entry, and vice versa. Protects against silent drift when someone adds
|
|
1085
|
+
or removes a factory without updating the golden table."""
|
|
1086
|
+
registered = {qn for qn, td in _type_defs.items() if td.type_factory is not None}
|
|
1087
|
+
missing = registered - set(FACTORY_SNAPSHOT)
|
|
1088
|
+
assert not missing, (
|
|
1089
|
+
f"Registered factory qnames without a FACTORY_SNAPSHOT entry: {missing}."
|
|
1090
|
+
)
|
|
1091
|
+
extra = set(FACTORY_SNAPSHOT) - registered
|
|
1092
|
+
assert not extra, (
|
|
1093
|
+
f"FACTORY_SNAPSHOT entries that aren't registered factories: {extra}."
|
|
1094
|
+
)
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
@pytest.mark.parametrize("qname", sorted(FACTORY_SNAPSHOT))
|
|
1098
|
+
def test_factory_param_kinds_match_snapshot(qname):
|
|
1099
|
+
"""`TypeDef.param_kinds` must match the golden arity/kinds table."""
|
|
1100
|
+
td = get_type_def(qname)
|
|
1101
|
+
assert td is not None
|
|
1102
|
+
expected = tuple(_k(name) for name in FACTORY_SNAPSHOT[qname])
|
|
1103
|
+
actual = tuple(td.param_kinds)
|
|
1104
|
+
assert actual == expected, (
|
|
1105
|
+
f"{qname}: TypeDef.param_kinds={actual!r} but snapshot={expected!r}"
|
|
1106
|
+
)
|
|
1107
|
+
|
|
1108
|
+
|
|
1109
|
+
def test_factory_produces_same_instance_as_registry():
|
|
1110
|
+
"""`TypeDef.type_factory(...)` with canonical args must produce a
|
|
1111
|
+
TpyType whose qualified name equals the TypeDef qname. Catches wiring
|
|
1112
|
+
mistakes (e.g. list's factory producing a dict) without pinning every
|
|
1113
|
+
factory's exact return value."""
|
|
1114
|
+
I32 = ts.INT32
|
|
1115
|
+
canonical_args: dict[str, tuple] = {
|
|
1116
|
+
"builtins.list": (I32,),
|
|
1117
|
+
"builtins.dict": (ts.STR, I32),
|
|
1118
|
+
"builtins.dict_keys": (ts.STR, I32),
|
|
1119
|
+
"builtins.dict_values": (ts.STR, I32),
|
|
1120
|
+
"builtins.dict_items": (ts.STR, I32),
|
|
1121
|
+
"builtins.set": (I32,),
|
|
1122
|
+
"builtins.Range": (I32,),
|
|
1123
|
+
"tpy.Array": (I32, 10),
|
|
1124
|
+
"tpy.Span": (I32,),
|
|
1125
|
+
"tpy.SpanIter": (I32,),
|
|
1126
|
+
"tpy.Ptr": (I32,),
|
|
1127
|
+
}
|
|
1128
|
+
for qname, args in canonical_args.items():
|
|
1129
|
+
td = get_type_def(qname)
|
|
1130
|
+
assert td is not None and td.type_factory is not None
|
|
1131
|
+
produced = td.type_factory(*args)
|
|
1132
|
+
assert produced.qualified_name() == qname, (
|
|
1133
|
+
f"{qname}: factory produced {produced!r} with qname "
|
|
1134
|
+
f"{produced.qualified_name()!r}"
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
# Zero-arg factories must return the typesys singleton with the same qname.
|
|
1138
|
+
zero_arg_qnames = [qn for qn, kinds in FACTORY_SNAPSHOT.items() if not kinds]
|
|
1139
|
+
for qname in zero_arg_qnames:
|
|
1140
|
+
td = get_type_def(qname)
|
|
1141
|
+
assert td is not None and td.type_factory is not None
|
|
1142
|
+
produced = td.type_factory()
|
|
1143
|
+
assert produced.qualified_name() == qname, (
|
|
1144
|
+
f"{qname}: zero-arg factory produced {produced!r} with qname "
|
|
1145
|
+
f"{produced.qualified_name()!r}"
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
|
|
1149
|
+
def test_structural_wrapper_category_only_holds_ptr():
|
|
1150
|
+
"""The STRUCTURAL_WRAPPER category exists only for `tpy.Ptr`. If a new
|
|
1151
|
+
entry gets added, the author must update the resolver / sema paths
|
|
1152
|
+
that currently key on Ptr being the sole structural-wrapper factory."""
|
|
1153
|
+
wrappers = {qn for qn, td in _type_defs.items()
|
|
1154
|
+
if td.category is TypeCategory.STRUCTURAL_WRAPPER}
|
|
1155
|
+
assert wrappers == {"tpy.Ptr"}, (
|
|
1156
|
+
f"Unexpected STRUCTURAL_WRAPPER entries: {wrappers}"
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def test_find_factory_helpers_match_old_lookup():
|
|
1161
|
+
"""`find_factory_by_simple_name` and `find_factory_in_module` must
|
|
1162
|
+
resolve to the same qname as the old `lookup_generic_type` /
|
|
1163
|
+
`lookup_generic_type_in_module` factory table."""
|
|
1164
|
+
from tpyc.type_def_registry import (
|
|
1165
|
+
find_factory_by_simple_name, find_factory_in_module,
|
|
1166
|
+
factory_qnames_in_module,
|
|
1167
|
+
)
|
|
1168
|
+
# Simple-name lookup scans builtins then tpy.
|
|
1169
|
+
assert find_factory_by_simple_name("list").qname == "builtins.list"
|
|
1170
|
+
assert find_factory_by_simple_name("Span").qname == "tpy.Span"
|
|
1171
|
+
assert find_factory_by_simple_name("Ptr").qname == "tpy.Ptr"
|
|
1172
|
+
assert find_factory_by_simple_name("definitely_unknown") is None
|
|
1173
|
+
|
|
1174
|
+
# Module-qualified lookup.
|
|
1175
|
+
assert find_factory_in_module("Array", "tpy").qname == "tpy.Array"
|
|
1176
|
+
assert find_factory_in_module("list", "builtins").qname == "builtins.list"
|
|
1177
|
+
# Wrong module -> None.
|
|
1178
|
+
assert find_factory_in_module("Array", "builtins") is None
|
|
1179
|
+
assert find_factory_in_module("list", "tpy") is None
|
|
1180
|
+
|
|
1181
|
+
# Enumeration helper: every tpy.* factory qname must appear.
|
|
1182
|
+
tpy_factories = set(factory_qnames_in_module("tpy"))
|
|
1183
|
+
expected_tpy = {qn for qn in FACTORY_SNAPSHOT if qn.startswith("tpy.")}
|
|
1184
|
+
assert tpy_factories == expected_tpy
|
|
1185
|
+
|
|
1186
|
+
|
|
1187
|
+
def test_fixed_int_names_stay_in_sync():
|
|
1188
|
+
"""Three modules carry a `_FIXED_INT_NAMES` set (one hardcoded in
|
|
1189
|
+
parser after F.3f.3; two derived from ALL_FIXED_INTS in codegen
|
|
1190
|
+
statements and macro_api). If a new fixed-int width ever lands in
|
|
1191
|
+
typesys.ALL_FIXED_INTS, the hardcoded copy in parser must update in
|
|
1192
|
+
lockstep; this test pins the invariant so a single addition surfaces
|
|
1193
|
+
all sites at once.
|
|
1194
|
+
|
|
1195
|
+
codegen_cpp.functions._SCALAR_ZERO_CTOR_NAMES is intentionally
|
|
1196
|
+
broader (adds `int`/`float`/`bool` for zero-arg scalar ctor codegen)
|
|
1197
|
+
and is not covered here.
|
|
1198
|
+
"""
|
|
1199
|
+
expected = frozenset(str(t) for t in ALL_FIXED_INTS)
|
|
1200
|
+
assert PARSER_FIXED_INT_NAMES == expected, (
|
|
1201
|
+
f"parser._FIXED_INT_NAMES drifted from ALL_FIXED_INTS: "
|
|
1202
|
+
f"missing={expected - PARSER_FIXED_INT_NAMES}, "
|
|
1203
|
+
f"extra={PARSER_FIXED_INT_NAMES - expected}"
|
|
1204
|
+
)
|
|
1205
|
+
assert StatementGenerator._FIXED_INT_NAMES == expected
|
|
1206
|
+
assert MACRO_FIXED_INT_NAMES == expected
|
|
1207
|
+
|
|
1208
|
+
|
|
1209
|
+
# =========================================================================
|
|
1210
|
+
# Protocol snapshot (TypeRegistry.protocols retirement).
|
|
1211
|
+
#
|
|
1212
|
+
# Hand-written golden table of ProtocolInfo fields for every stdlib
|
|
1213
|
+
# protocol the compiler registers when `typing` + `tpy` are imported.
|
|
1214
|
+
# The retirement of the short-name `TypeRegistry.protocols` dict in
|
|
1215
|
+
# favor of qname-keyed `TypeDef.protocol` is an invariance refactor:
|
|
1216
|
+
# every qname below must still resolve to the same ProtocolInfo
|
|
1217
|
+
# payload after each migration step (flipping callers to
|
|
1218
|
+
# `protocol_info_of`, reshaping storage, deleting the legacy dict).
|
|
1219
|
+
#
|
|
1220
|
+
# Mirrors PRIMITIVE_SNAPSHOT (primitive migration) and ENUM_SNAPSHOT
|
|
1221
|
+
# (enum migration). Hand-maintained -- regenerating from the registry
|
|
1222
|
+
# would defeat the check.
|
|
1223
|
+
# =========================================================================
|
|
1224
|
+
|
|
1225
|
+
|
|
1226
|
+
PROTOCOL_SNAPSHOT: dict[str, dict] = {
|
|
1227
|
+
# typing.* protocols (tpy._typing, collapsed via cpp_namespace="tpystd::typing")
|
|
1228
|
+
"typing.Sized": dict(
|
|
1229
|
+
name="Sized", module="typing",
|
|
1230
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1231
|
+
cpp_concept=None,
|
|
1232
|
+
type_params=(), parent_protocols=(),
|
|
1233
|
+
methods=("__len__",),
|
|
1234
|
+
),
|
|
1235
|
+
"typing.Sequence": dict(
|
|
1236
|
+
name="Sequence", module="typing",
|
|
1237
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1238
|
+
cpp_concept=None,
|
|
1239
|
+
type_params=("T",), parent_protocols=(),
|
|
1240
|
+
methods=("__getitem__", "__len__"),
|
|
1241
|
+
),
|
|
1242
|
+
"typing.MutableSequence": dict(
|
|
1243
|
+
name="MutableSequence", module="typing",
|
|
1244
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1245
|
+
cpp_concept=None,
|
|
1246
|
+
type_params=("T",), parent_protocols=(),
|
|
1247
|
+
methods=("__getitem__", "__len__", "__setitem__"),
|
|
1248
|
+
),
|
|
1249
|
+
"typing.Iterator": dict(
|
|
1250
|
+
name="Iterator", module="typing",
|
|
1251
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1252
|
+
cpp_concept=None,
|
|
1253
|
+
type_params=("T",), parent_protocols=(),
|
|
1254
|
+
methods=("__iter__", "__next__"),
|
|
1255
|
+
),
|
|
1256
|
+
"typing.Iterable": dict(
|
|
1257
|
+
name="Iterable", module="typing",
|
|
1258
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1259
|
+
cpp_concept=None,
|
|
1260
|
+
type_params=("T",), parent_protocols=(),
|
|
1261
|
+
methods=("__iter__",),
|
|
1262
|
+
),
|
|
1263
|
+
# tpy.* @dynamic protocols (tpy._core._types)
|
|
1264
|
+
"tpy.Throwable": dict(
|
|
1265
|
+
name="Throwable", module="tpy",
|
|
1266
|
+
is_dynamic=True, is_marker=False, is_readonly=False,
|
|
1267
|
+
cpp_concept="::tpy::Throwable",
|
|
1268
|
+
type_params=(), parent_protocols=(),
|
|
1269
|
+
methods=("__raise__", "clone"),
|
|
1270
|
+
),
|
|
1271
|
+
# tpy.* structural protocols (tpy._core._types)
|
|
1272
|
+
"tpy.coro.Awaitable": dict(
|
|
1273
|
+
name="Awaitable", module="tpy.coro",
|
|
1274
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1275
|
+
cpp_concept=None,
|
|
1276
|
+
type_params=("T",), parent_protocols=(),
|
|
1277
|
+
methods=("__poll__",),
|
|
1278
|
+
),
|
|
1279
|
+
"tpy.coro.Awaker": dict(
|
|
1280
|
+
name="Awaker", module="tpy.coro",
|
|
1281
|
+
is_dynamic=True, is_marker=False, is_readonly=False,
|
|
1282
|
+
cpp_concept=None,
|
|
1283
|
+
type_params=(), parent_protocols=(),
|
|
1284
|
+
methods=("mark_runnable",),
|
|
1285
|
+
),
|
|
1286
|
+
"tpy.coro.Cancellable": dict(
|
|
1287
|
+
name="Cancellable", module="tpy.coro",
|
|
1288
|
+
is_dynamic=True, is_marker=False, is_readonly=False,
|
|
1289
|
+
cpp_concept=None,
|
|
1290
|
+
type_params=("T",), parent_protocols=(),
|
|
1291
|
+
methods=("__poll__", "cancel"),
|
|
1292
|
+
),
|
|
1293
|
+
"tpy.Truthy": dict(
|
|
1294
|
+
name="Truthy", module="tpy",
|
|
1295
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1296
|
+
cpp_concept=None,
|
|
1297
|
+
type_params=(), parent_protocols=(),
|
|
1298
|
+
methods=("__bool__",),
|
|
1299
|
+
),
|
|
1300
|
+
"tpy.Stringable": dict(
|
|
1301
|
+
name="Stringable", module="tpy",
|
|
1302
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1303
|
+
cpp_concept=None,
|
|
1304
|
+
type_params=(), parent_protocols=(),
|
|
1305
|
+
methods=("__str__",),
|
|
1306
|
+
),
|
|
1307
|
+
"tpy.Representable": dict(
|
|
1308
|
+
name="Representable", module="tpy",
|
|
1309
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1310
|
+
cpp_concept=None,
|
|
1311
|
+
type_params=(), parent_protocols=(),
|
|
1312
|
+
methods=("__repr__",),
|
|
1313
|
+
),
|
|
1314
|
+
"tpy.Hashable": dict(
|
|
1315
|
+
name="Hashable", module="tpy",
|
|
1316
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1317
|
+
cpp_concept=None,
|
|
1318
|
+
type_params=(), parent_protocols=(),
|
|
1319
|
+
methods=("__hash__",),
|
|
1320
|
+
),
|
|
1321
|
+
"tpy.Comparable": dict(
|
|
1322
|
+
name="Comparable", module="tpy",
|
|
1323
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1324
|
+
cpp_concept=None,
|
|
1325
|
+
type_params=(), parent_protocols=(),
|
|
1326
|
+
methods=("__lt__",),
|
|
1327
|
+
),
|
|
1328
|
+
"tpy.Equatable": dict(
|
|
1329
|
+
name="Equatable", module="tpy",
|
|
1330
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1331
|
+
cpp_concept=None,
|
|
1332
|
+
type_params=(), parent_protocols=(),
|
|
1333
|
+
methods=("__eq__",),
|
|
1334
|
+
),
|
|
1335
|
+
"tpy.Deref": dict(
|
|
1336
|
+
name="Deref", module="tpy",
|
|
1337
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1338
|
+
cpp_concept=None,
|
|
1339
|
+
type_params=("T",), parent_protocols=(),
|
|
1340
|
+
methods=("__deref__",),
|
|
1341
|
+
),
|
|
1342
|
+
"tpy.Spannable": dict(
|
|
1343
|
+
name="Spannable", module="tpy",
|
|
1344
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1345
|
+
cpp_concept=None,
|
|
1346
|
+
type_params=("T",), parent_protocols=(),
|
|
1347
|
+
methods=("__span__",),
|
|
1348
|
+
),
|
|
1349
|
+
"tpy.Writable": dict(
|
|
1350
|
+
name="Writable", module="tpy",
|
|
1351
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1352
|
+
cpp_concept=None,
|
|
1353
|
+
type_params=(), parent_protocols=(),
|
|
1354
|
+
methods=("flush", "write"),
|
|
1355
|
+
),
|
|
1356
|
+
"tpy.Readable": dict(
|
|
1357
|
+
name="Readable", module="tpy",
|
|
1358
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1359
|
+
cpp_concept=None,
|
|
1360
|
+
type_params=(), parent_protocols=(),
|
|
1361
|
+
methods=("read", "readline"),
|
|
1362
|
+
),
|
|
1363
|
+
"tpy.BinaryWritable": dict(
|
|
1364
|
+
name="BinaryWritable", module="tpy",
|
|
1365
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1366
|
+
cpp_concept=None,
|
|
1367
|
+
type_params=(), parent_protocols=(),
|
|
1368
|
+
methods=("flush", "write"),
|
|
1369
|
+
),
|
|
1370
|
+
"tpy.BinaryReadable": dict(
|
|
1371
|
+
name="BinaryReadable", module="tpy",
|
|
1372
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1373
|
+
cpp_concept=None,
|
|
1374
|
+
type_params=(), parent_protocols=(),
|
|
1375
|
+
methods=("read", "readline"),
|
|
1376
|
+
),
|
|
1377
|
+
"tpy.Seekable": dict(
|
|
1378
|
+
name="Seekable", module="tpy",
|
|
1379
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1380
|
+
cpp_concept=None,
|
|
1381
|
+
type_params=(), parent_protocols=(),
|
|
1382
|
+
methods=("seek", "tell"),
|
|
1383
|
+
),
|
|
1384
|
+
"tpy.Closable": dict(
|
|
1385
|
+
name="Closable", module="tpy",
|
|
1386
|
+
is_dynamic=False, is_marker=False, is_readonly=False,
|
|
1387
|
+
cpp_concept=None,
|
|
1388
|
+
type_params=(), parent_protocols=(),
|
|
1389
|
+
methods=("close",),
|
|
1390
|
+
),
|
|
1391
|
+
# tpy.* @native marker / concept-backed protocols
|
|
1392
|
+
"tpy.NativeIterable": dict(
|
|
1393
|
+
name="NativeIterable", module="tpy",
|
|
1394
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1395
|
+
cpp_concept="::tpy::NativeIterable",
|
|
1396
|
+
type_params=("T",),
|
|
1397
|
+
parent_protocols=(
|
|
1398
|
+
ts.NominalType(
|
|
1399
|
+
name="Iterable",
|
|
1400
|
+
type_args=(ts.TypeParamRef(
|
|
1401
|
+
name="T", bound=None, kind=ts.TypeParamKind.TYPE,
|
|
1402
|
+
),),
|
|
1403
|
+
is_protocol=True,
|
|
1404
|
+
_module_qname="typing.Iterable",
|
|
1405
|
+
),
|
|
1406
|
+
),
|
|
1407
|
+
methods=(),
|
|
1408
|
+
),
|
|
1409
|
+
"tpy.NativeRangeConstructible": dict(
|
|
1410
|
+
name="NativeRangeConstructible", module="tpy",
|
|
1411
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1412
|
+
cpp_concept="::tpy::NativeRangeConstructible",
|
|
1413
|
+
type_params=("T",), parent_protocols=(),
|
|
1414
|
+
methods=(),
|
|
1415
|
+
),
|
|
1416
|
+
"tpy.ValueType": dict(
|
|
1417
|
+
name="ValueType", module="tpy",
|
|
1418
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1419
|
+
cpp_concept="::tpy::ValueType",
|
|
1420
|
+
type_params=(), parent_protocols=(),
|
|
1421
|
+
methods=(),
|
|
1422
|
+
),
|
|
1423
|
+
"tpy.Copyable": dict(
|
|
1424
|
+
name="Copyable", module="tpy",
|
|
1425
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1426
|
+
cpp_concept="::tpy::Copyable",
|
|
1427
|
+
type_params=(), parent_protocols=(),
|
|
1428
|
+
methods=(),
|
|
1429
|
+
),
|
|
1430
|
+
"tpy.Send": dict(
|
|
1431
|
+
name="Send", module="tpy",
|
|
1432
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1433
|
+
cpp_concept="::tpy::Send",
|
|
1434
|
+
type_params=(), parent_protocols=(),
|
|
1435
|
+
methods=(),
|
|
1436
|
+
),
|
|
1437
|
+
"tpy.Sync": dict(
|
|
1438
|
+
name="Sync", module="tpy",
|
|
1439
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1440
|
+
cpp_concept="::tpy::Sync",
|
|
1441
|
+
type_params=(), parent_protocols=(),
|
|
1442
|
+
methods=(),
|
|
1443
|
+
),
|
|
1444
|
+
"tpy.Default": dict(
|
|
1445
|
+
name="Default", module="tpy",
|
|
1446
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1447
|
+
cpp_concept="::std::default_initializable",
|
|
1448
|
+
type_params=(), parent_protocols=(),
|
|
1449
|
+
methods=(),
|
|
1450
|
+
),
|
|
1451
|
+
"tpy.ReturnException": dict(
|
|
1452
|
+
name="ReturnException", module="tpy",
|
|
1453
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1454
|
+
cpp_concept="::tpy::ReturnException",
|
|
1455
|
+
type_params=(), parent_protocols=(),
|
|
1456
|
+
methods=(),
|
|
1457
|
+
),
|
|
1458
|
+
"tpy.Covariant": dict(
|
|
1459
|
+
name="Covariant", module="tpy",
|
|
1460
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1461
|
+
cpp_concept="::tpy::Covariant",
|
|
1462
|
+
type_params=("T",), parent_protocols=(),
|
|
1463
|
+
methods=(),
|
|
1464
|
+
),
|
|
1465
|
+
"tpy.AnyFixedInt": dict(
|
|
1466
|
+
name="AnyFixedInt", module="tpy",
|
|
1467
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1468
|
+
cpp_concept="::tpy::AnyFixedInt",
|
|
1469
|
+
type_params=(), parent_protocols=(),
|
|
1470
|
+
methods=(),
|
|
1471
|
+
),
|
|
1472
|
+
"tpy.AnyFixedSigned": dict(
|
|
1473
|
+
name="AnyFixedSigned", module="tpy",
|
|
1474
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1475
|
+
cpp_concept="::tpy::AnyFixedSigned",
|
|
1476
|
+
type_params=(), parent_protocols=(),
|
|
1477
|
+
methods=(),
|
|
1478
|
+
),
|
|
1479
|
+
"tpy.AnyFixedUnsigned": dict(
|
|
1480
|
+
name="AnyFixedUnsigned", module="tpy",
|
|
1481
|
+
is_dynamic=False, is_marker=True, is_readonly=False,
|
|
1482
|
+
cpp_concept="::tpy::AnyFixedUnsigned",
|
|
1483
|
+
type_params=(), parent_protocols=(),
|
|
1484
|
+
methods=(),
|
|
1485
|
+
),
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
_PROTOCOL_SNAPSHOT_SOURCE = """\
|
|
1490
|
+
from typing import Sized, Iterable, Iterator, Sequence, MutableSequence
|
|
1491
|
+
from tpy import (
|
|
1492
|
+
Truthy, Stringable, Representable, Hashable, Comparable, Equatable,
|
|
1493
|
+
Deref, Spannable, Writable, Readable, BinaryWritable, BinaryReadable,
|
|
1494
|
+
Seekable, Closable,
|
|
1495
|
+
NativeIterable, NativeRangeConstructible, ValueType, Send, Sync,
|
|
1496
|
+
Default, ReturnException, Covariant,
|
|
1497
|
+
AnyFixedInt, AnyFixedSigned, AnyFixedUnsigned,
|
|
1498
|
+
)
|
|
1499
|
+
from tpy.coro import Awaitable, Cancellable
|
|
1500
|
+
"""
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
@pytest.fixture(scope="module")
|
|
1504
|
+
def _protocol_snapshot_compiled():
|
|
1505
|
+
"""Compile a source that imports every protocol in PROTOCOL_SNAPSHOT.
|
|
1506
|
+
|
|
1507
|
+
Scoped to the module so the compile runs once; the conftest autouse
|
|
1508
|
+
fixture clears dynamic state around each test, so individual tests
|
|
1509
|
+
that need the populated registry recompile via this fixture.
|
|
1510
|
+
"""
|
|
1511
|
+
from tpyc import get_lib_dir
|
|
1512
|
+
from tpyc.compiler import Compiler
|
|
1513
|
+
compiler = Compiler.from_source(
|
|
1514
|
+
_PROTOCOL_SNAPSHOT_SOURCE, lib_dirs=[get_lib_dir() / "tpy"]
|
|
1515
|
+
)
|
|
1516
|
+
compiler.compile()
|
|
1517
|
+
return compiler
|
|
1518
|
+
|
|
1519
|
+
|
|
1520
|
+
@pytest.fixture
|
|
1521
|
+
def _protocol_registry(_protocol_snapshot_compiled):
|
|
1522
|
+
"""Per-test fixture: recompile the snapshot source so the TypeDef
|
|
1523
|
+
registry is populated inside the test's `clear_all_compilation_state`
|
|
1524
|
+
window. The module-scoped fixture above is retained for any future
|
|
1525
|
+
callers that legitimately want the pre-clear state.
|
|
1526
|
+
|
|
1527
|
+
The fixture keeps the recompiled Compiler active for the test body
|
|
1528
|
+
so reads of `compiler.dynamic_type_defs` (via `get_type_def` etc.)
|
|
1529
|
+
see this compilation's slice, not the conftest fake context's empty
|
|
1530
|
+
one.
|
|
1531
|
+
"""
|
|
1532
|
+
from tpyc import get_lib_dir
|
|
1533
|
+
from tpyc.compiler import Compiler
|
|
1534
|
+
compiler = Compiler.from_source(
|
|
1535
|
+
_PROTOCOL_SNAPSHOT_SOURCE, lib_dirs=[get_lib_dir() / "tpy"]
|
|
1536
|
+
)
|
|
1537
|
+
compiler.compile()
|
|
1538
|
+
with activate_compiler(compiler):
|
|
1539
|
+
yield compiler
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
def test_protocol_snapshot_covers_every_compiled_protocol(_protocol_registry):
|
|
1543
|
+
"""Every PROTOCOL-category TypeDef the snapshot compile populates must
|
|
1544
|
+
have a PROTOCOL_SNAPSHOT entry (and vice versa). Guards against silent
|
|
1545
|
+
drift: a new stdlib protocol without a snapshot entry, or a snapshot
|
|
1546
|
+
entry pointing at a qname the registration path no longer creates."""
|
|
1547
|
+
# PROTOCOL TypeDefs live in two places: pre-existing static entries
|
|
1548
|
+
# whose payload was attached during compile (mostly @builtin_type
|
|
1549
|
+
# stub protocols), and purely-dynamic entries on the active
|
|
1550
|
+
# compiler's `dynamic_type_defs`. Both contribute.
|
|
1551
|
+
live = {
|
|
1552
|
+
qn for qn, td in _type_defs.items()
|
|
1553
|
+
if td.category is TypeCategory.PROTOCOL and td.protocol is not None
|
|
1554
|
+
}
|
|
1555
|
+
live |= {
|
|
1556
|
+
qn for qn, td in _protocol_registry.dynamic_type_defs.items()
|
|
1557
|
+
if td.category is TypeCategory.PROTOCOL and td.protocol is not None
|
|
1558
|
+
}
|
|
1559
|
+
missing = live - set(PROTOCOL_SNAPSHOT)
|
|
1560
|
+
assert not missing, (
|
|
1561
|
+
f"Protocol qnames registered by the snapshot compile without a "
|
|
1562
|
+
f"PROTOCOL_SNAPSHOT entry: {sorted(missing)}. Add them with "
|
|
1563
|
+
f"their expected ProtocolInfo fields."
|
|
1564
|
+
)
|
|
1565
|
+
extra = set(PROTOCOL_SNAPSHOT) - live
|
|
1566
|
+
assert not extra, (
|
|
1567
|
+
f"PROTOCOL_SNAPSHOT entries that the compile didn't register: "
|
|
1568
|
+
f"{sorted(extra)}. Either the registration path regressed or the "
|
|
1569
|
+
f"snapshot names a qname that no longer exists."
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
|
|
1573
|
+
@pytest.mark.parametrize("qname", sorted(PROTOCOL_SNAPSHOT))
|
|
1574
|
+
def test_protocol_type_def_matches_snapshot(qname, _protocol_registry):
|
|
1575
|
+
"""Hand-coded expected values for every stdlib protocol survive the
|
|
1576
|
+
retirement of TypeRegistry.protocols. Reading goes through
|
|
1577
|
+
`TypeDef.protocol` (qname-keyed), which is authoritative; after each
|
|
1578
|
+
migration step, every field below must still match."""
|
|
1579
|
+
td = get_type_def(qname)
|
|
1580
|
+
assert td is not None, f"No TypeDef for {qname}"
|
|
1581
|
+
assert td.category is TypeCategory.PROTOCOL, (
|
|
1582
|
+
f"{qname}: TypeDef.category={td.category}, expected PROTOCOL"
|
|
1583
|
+
)
|
|
1584
|
+
info = td.protocol
|
|
1585
|
+
assert info is not None, f"{qname}: TypeDef.protocol payload missing"
|
|
1586
|
+
expected = PROTOCOL_SNAPSHOT[qname]
|
|
1587
|
+
|
|
1588
|
+
assert info.name == expected["name"]
|
|
1589
|
+
assert info.module == expected["module"]
|
|
1590
|
+
assert info.is_dynamic == expected["is_dynamic"]
|
|
1591
|
+
assert info.is_marker == expected["is_marker"]
|
|
1592
|
+
assert info.is_readonly == expected["is_readonly"]
|
|
1593
|
+
assert info.cpp_concept == expected["cpp_concept"]
|
|
1594
|
+
assert tuple(info.type_params) == expected["type_params"]
|
|
1595
|
+
assert tuple(info.parent_protocols) == expected["parent_protocols"]
|
|
1596
|
+
assert tuple(sorted(m.name for m in info.methods)) == expected["methods"]
|
|
1597
|
+
|
|
1598
|
+
|
|
1599
|
+
@pytest.mark.parametrize("qname", sorted(PROTOCOL_SNAPSHOT))
|
|
1600
|
+
def test_protocol_info_of_matches_type_def(qname, _protocol_registry):
|
|
1601
|
+
"""`protocol_info_of(NominalType(qname))` must return the same
|
|
1602
|
+
ProtocolInfo instance as the direct `TypeDef.protocol` lookup. This
|
|
1603
|
+
pins the migration target: all callers eventually route through
|
|
1604
|
+
`protocol_info_of` and must see identical data."""
|
|
1605
|
+
from tpyc.type_def_registry import protocol_info_of
|
|
1606
|
+
td = get_type_def(qname)
|
|
1607
|
+
assert td is not None and td.protocol is not None
|
|
1608
|
+
# Synthesize a NominalType carrying the qname; protocol_info_of goes
|
|
1609
|
+
# through `type_def_of(t)` which keys on `qualified_name()`.
|
|
1610
|
+
module, _, short = qname.rpartition(".")
|
|
1611
|
+
inst = ts.NominalType(
|
|
1612
|
+
name=short, type_args=(), is_protocol=True, _module_qname=qname,
|
|
1613
|
+
)
|
|
1614
|
+
info = protocol_info_of(inst)
|
|
1615
|
+
assert info is td.protocol, (
|
|
1616
|
+
f"{qname}: protocol_info_of returned {info!r}, TypeDef.protocol={td.protocol!r}"
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
|
|
1620
|
+
def test_scan_by_short_name_honors_import_alias():
|
|
1621
|
+
"""`register_protocol(info, local_name)` must make `scan_by_short_name`
|
|
1622
|
+
resolve both the protocol's canonical short name and any local import
|
|
1623
|
+
alias to the same `ProtocolInfo`.
|
|
1624
|
+
|
|
1625
|
+
The alias path is the load-bearing reason the short-name
|
|
1626
|
+
`_protocols_by_local_name` table exists at all; without it the
|
|
1627
|
+
short-name helper could be replaced by a scan over qnames. Pin the
|
|
1628
|
+
semantics here so the helper's contract doesn't silently regress when
|
|
1629
|
+
the storage layer evolves further (e.g. a future move to TypeDef
|
|
1630
|
+
discovery).
|
|
1631
|
+
|
|
1632
|
+
Uses a synthetic ProtocolInfo rather than a real compile so the test
|
|
1633
|
+
stays fast and doesn't depend on the multi-module import plumbing.
|
|
1634
|
+
"""
|
|
1635
|
+
registry = ts.TypeRegistry()
|
|
1636
|
+
info = ts.ProtocolInfo(
|
|
1637
|
+
name="Sized",
|
|
1638
|
+
methods=[],
|
|
1639
|
+
module="typing",
|
|
1640
|
+
)
|
|
1641
|
+
registry.register_protocol(info, "MySized")
|
|
1642
|
+
|
|
1643
|
+
# Alias resolves to the original ProtocolInfo.
|
|
1644
|
+
assert registry.scan_by_short_name("MySized") is info
|
|
1645
|
+
|
|
1646
|
+
# Canonical name is NOT auto-aliased when a local name is provided;
|
|
1647
|
+
# only the explicit binding is recorded.
|
|
1648
|
+
assert registry.scan_by_short_name("Sized") is None
|
|
1649
|
+
|
|
1650
|
+
# A second registration without a local-name override exposes the
|
|
1651
|
+
# canonical short name too -- mimicking the typical stdlib-import
|
|
1652
|
+
# path in `compiler.py::_analyze_module`.
|
|
1653
|
+
registry.register_protocol(info)
|
|
1654
|
+
assert registry.scan_by_short_name("Sized") is info
|
|
1655
|
+
assert registry.scan_by_short_name("MySized") is info # alias still works
|
|
1656
|
+
|
|
1657
|
+
# Unknown name returns None, not a random collision.
|
|
1658
|
+
assert registry.scan_by_short_name("Nonexistent") is None
|
|
1659
|
+
|
|
1660
|
+
|
|
1661
|
+
def test_resolve_type_for_codegen_does_not_promote_record_to_protocol():
|
|
1662
|
+
"""`ProtocolGenerator.resolve_type_for_codegen` probes `protocol_info_of`
|
|
1663
|
+
with `is_protocol=True` set on the NominalType. When `_module_qname`
|
|
1664
|
+
points at a RECORD-category TypeDef (short name happens to collide with
|
|
1665
|
+
a protocol in another module), the probe must return None and the
|
|
1666
|
+
type must stay classified as a record.
|
|
1667
|
+
|
|
1668
|
+
The pre-P.1 path used a short-name `registry.get_protocol(typ.name)`
|
|
1669
|
+
lookup that would have matched the collision and silently promoted
|
|
1670
|
+
the record. Pinning the qname-driven behavior here prevents a
|
|
1671
|
+
regression to the short-name semantics during any future reshape of
|
|
1672
|
+
`resolve_type_for_codegen`.
|
|
1673
|
+
"""
|
|
1674
|
+
from tpyc.type_def_registry import (
|
|
1675
|
+
attach_dynamic_type_def, TypeCategory,
|
|
1676
|
+
)
|
|
1677
|
+
# A user record at `user_mod.Widget`.
|
|
1678
|
+
record_qname = "user_mod.Widget"
|
|
1679
|
+
record_info = ts.RecordInfo(
|
|
1680
|
+
name="Widget",
|
|
1681
|
+
module="user_mod",
|
|
1682
|
+
fields=[],
|
|
1683
|
+
methods={},
|
|
1684
|
+
)
|
|
1685
|
+
attach_dynamic_type_def(record_qname, TypeCategory.RECORD, record=record_info)
|
|
1686
|
+
|
|
1687
|
+
# An unrelated protocol also named `Widget` in a different module --
|
|
1688
|
+
# this would have short-name-matched under the old lookup.
|
|
1689
|
+
protocol_qname = "other_mod.Widget"
|
|
1690
|
+
protocol_info = ts.ProtocolInfo(
|
|
1691
|
+
name="Widget", methods=[], module="other_mod",
|
|
1692
|
+
)
|
|
1693
|
+
attach_dynamic_type_def(protocol_qname, TypeCategory.PROTOCOL,
|
|
1694
|
+
protocol=protocol_info)
|
|
1695
|
+
|
|
1696
|
+
# NominalType bound to the record qname, not flagged as protocol.
|
|
1697
|
+
record_nominal = ts.NominalType(
|
|
1698
|
+
name="Widget", type_args=(), is_protocol=False,
|
|
1699
|
+
_module_qname=record_qname,
|
|
1700
|
+
)
|
|
1701
|
+
|
|
1702
|
+
# Build a minimal ProtocolGenerator with just enough context to call
|
|
1703
|
+
# resolve_type_for_codegen; the method only reaches into
|
|
1704
|
+
# `protocol_info_of`, not into `ctx.analyzer.*`.
|
|
1705
|
+
from tpyc.codegen_cpp.protocols import ProtocolGenerator
|
|
1706
|
+
|
|
1707
|
+
class _StubCtx:
|
|
1708
|
+
pass
|
|
1709
|
+
|
|
1710
|
+
pg = ProtocolGenerator(_StubCtx())
|
|
1711
|
+
resolved = pg.resolve_type_for_codegen(record_nominal)
|
|
1712
|
+
|
|
1713
|
+
# Record must NOT be flipped to a protocol.
|
|
1714
|
+
assert resolved is record_nominal, (
|
|
1715
|
+
"resolve_type_for_codegen must leave a record-qname NominalType "
|
|
1716
|
+
"unchanged even when a protocol of the same short name exists "
|
|
1717
|
+
f"elsewhere; got {resolved!r}"
|
|
1718
|
+
)
|
|
1719
|
+
assert resolved.is_protocol is False
|
|
1720
|
+
|
|
1721
|
+
# Sanity: a placeholder NominalType (no _module_qname) whose short
|
|
1722
|
+
# name resolves to the `other_mod` protocol via `_protocol_modules`
|
|
1723
|
+
# *should* get flipped -- this is the legitimate promotion case.
|
|
1724
|
+
from tpyc.typesys import register_protocol_module
|
|
1725
|
+
register_protocol_module("Widget", "other_mod")
|
|
1726
|
+
placeholder = ts.NominalType(name="Widget", type_args=(), is_protocol=False)
|
|
1727
|
+
promoted = pg.resolve_type_for_codegen(placeholder)
|
|
1728
|
+
assert promoted.is_protocol is True
|
|
1729
|
+
assert promoted.qualified_name() == protocol_qname
|