From 3e419485c3ba125fcd65c544a1f5f6be1bea79cd Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 20 Dec 2024 01:39:32 -0800 Subject: [PATCH 1/3] `PYBIND11_PLATFORM_ABI_ID` Modernization Continued (platforms other than MSVC) (#5439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * THIS IS JUST A START: First attempt to combine information from PR #4953 and PR #5437 * Include GXX_ABI and USE_CXX in the identifier Further constrain to GXX_ABI 1002 or greater and less than 2000, hopefully future proof by summarizing that as `1` along with CXX11 on or off. * style: pre-commit fixes * Use `gxx_abi_1xxx` and simplify the Clang string After discussions with Ralf Grosse-Kunstleve we think these would make good identifiers that are concise and clear. * Error if `_GLIBCXX_USE_CXX11_ABI` is not defined Within the `__GXX_ABI_VERSION` block this should always be defined, guard against unexpected defines and make the error obvious. * Change `usecxx11` to `use_cxx11_abi` for correspondence with `_GLIBCXX_USE_CXX11_ABI` (similarly to `gxx_abi` for `__GXX_ABI_VERSION`). * `PYBIND11_COMPILER_TYPE` overhaul, mainly: replace `_icc`, `_clang`, `_gcc` with `_system` * Add NVHPC (__PGI) to the list of compilers compatible with system compiler. See comment by @robertmaynard: https://github.com/pybind/pybind11/pull/5439#issuecomment-2498839010 * Fix oversight: remove __NVCOMPILER elif branch in PYBIND11_BUILD_ABI block. Also add comment pointing to this PR (#5439). * Revert "Fix oversight: remove __NVCOMPILER elif branch in PYBIND11_BUILD_ABI block." This reverts commit d412303e727923571bfd9b907809b1d73a045b34. * Revert "Add NVHPC (__PGI) to the list of compilers compatible with system compiler." This reverts commit 9fc951588594c48d4bfd440cd985ffee7be57ae4. * Define NVHPC PYBIND11_BUILD_ABI using __GNUC__, __GNUC_MINOR__, _GLIBCXX_USE_CXX11_ABI * Use _GLIBCXX_USE_CXX11_ABI to detect libstdc++, then assume that NVHPC is always in the 1xxx ABI family. * Enhance NVHPC comment and limited future proofing. * The `PYBIND11_STDLIB` is obsolete but kept around to maintain backward compatibility. * Move `PYBIND11_BUILD_TYPE` down in the file, so that the order of macro definitions is the same as in the list defining `PYBIND11_PLATFORM_ABI_ID` * Introduce `PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE`: This makes it possible to achieve these two goals: * Avoid the leading underscore in `PYBIND11_PLATFORM_ABI_ID` (see https://github.com/pybind/pybind11/pull/5439#issuecomment-2503762161) * Maintain backward compatibility for use cases as reported under https://github.com/pybind/pybind11/pull/5439#issuecomment-2510212677 `PYBIND11_INTERNALS_KIND` is removed in this commit to ensure that `PYBIND11_COMPILER_TYPE` is the first element of the `PYBIND11_PLATFORM_ABI_ID`, so that `PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE` can meaningfully be used as a prefix for `PYBIND11_PLATFORM_ABI_ID` in pybind11/detail/internals.h. * Apply suggestion by @isuruf, with revised comments (code is as suggested). * Make determination of `PYBIND11_COMPILER_TYPE` `"macos"` or `"glibc"` more general. The main motivation is to resolve these "Manylinux on 🐍 3.13t • GIL" and "Pyodide wheel" failures: ``` /__w/pybind11/pybind11/include/pybind11/conduit/pybind11_platform_abi_id.h:35:10: error: #error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." 35 | # error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." | ^~~~~ ``` (All other CI jobs succeeded.) Further thought: Essentially, under Linux and macOS the `PYBIND11_COMPILER_TYPE` is only for informational purposes. ABI compatibility is determined by the libstdc++ or libc++ ABI version. * Add `PYBIND11_COMPILER_TYPE` `emscripten` * Add `PYBIND11_COMPILER_TYPE` `graalvm` * Revert "Add `PYBIND11_COMPILER_TYPE` `graalvm`" This reverts commit 75da5fbfd95cb6f431123a92d9fb5885e6c71fc2. * Revert "Add `PYBIND11_COMPILER_TYPE` `emscripten`" This reverts commit e34dc8b511d9f4dba0d128f9cf043f23fd155894. * Revert "Make determination of `PYBIND11_COMPILER_TYPE` `"macos"` or `"glibc"` more general." This reverts commit 41daaa41fadd03ede9bdd9479c8ddf4d143330bf. * Revert "Apply suggestion by @isuruf, with revised comments (code is as suggested)." This reverts commit ca9e6990deb9a57f21cba0879499600addeadf06. * Remove `defined(__INTEL_COMPILER)` as suggested by @hpkfft under https://github.com/pybind/pybind11/pull/5439#discussion_r1889156543 --------- Co-authored-by: Marcus D. Hanwell Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .../conduit/pybind11_platform_abi_id.h | 81 +++++++++---------- include/pybind11/detail/internals.h | 4 +- 2 files changed, 42 insertions(+), 43 deletions(-) diff --git a/include/pybind11/conduit/pybind11_platform_abi_id.h b/include/pybind11/conduit/pybind11_platform_abi_id.h index a7733bcf5..d21fdc56d 100644 --- a/include/pybind11/conduit/pybind11_platform_abi_id.h +++ b/include/pybind11/conduit/pybind11_platform_abi_id.h @@ -12,51 +12,32 @@ #define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x #define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) -// On MSVC, debug and release builds are not ABI-compatible! -#if defined(_MSC_VER) && defined(_DEBUG) -# define PYBIND11_BUILD_TYPE "_debug" +#ifdef PYBIND11_COMPILER_TYPE +// // To maintain backward compatibility (see PR #5439). +# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "" #else -# define PYBIND11_BUILD_TYPE "" -#endif - -// Let's assume that different compilers are ABI-incompatible. -// A user can manually set this string if they know their -// compiler is compatible. -#ifndef PYBIND11_COMPILER_TYPE -# if defined(_MSC_VER) -# define PYBIND11_COMPILER_TYPE "_msvc" -# elif defined(__INTEL_COMPILER) -# define PYBIND11_COMPILER_TYPE "_icc" -# elif defined(__clang__) -# define PYBIND11_COMPILER_TYPE "_clang" -# elif defined(__PGI) -# define PYBIND11_COMPILER_TYPE "_pgi" -# elif defined(__MINGW32__) -# define PYBIND11_COMPILER_TYPE "_mingw" +# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_" +# if defined(__MINGW32__) +# define PYBIND11_COMPILER_TYPE "mingw" # elif defined(__CYGWIN__) -# define PYBIND11_COMPILER_TYPE "_gcc_cygwin" -# elif defined(__GNUC__) -# define PYBIND11_COMPILER_TYPE "_gcc" +# define PYBIND11_COMPILER_TYPE "gcc_cygwin" +# elif defined(_MSC_VER) +# define PYBIND11_COMPILER_TYPE "msvc" +# elif defined(__clang__) || defined(__GNUC__) +# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler. # else -# define PYBIND11_COMPILER_TYPE "_unknown" +# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE." # endif #endif -// Also standard libs +// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the +// wild. Therefore, to maintain backward compatibility, it is kept around. #ifndef PYBIND11_STDLIB -# if defined(_LIBCPP_VERSION) -# define PYBIND11_STDLIB "_libcpp" -# elif defined(__GLIBCXX__) || defined(__GLIBCPP__) -# define PYBIND11_STDLIB "_libstdcpp" -# else -# define PYBIND11_STDLIB "" -# endif +# define PYBIND11_STDLIB "" #endif #ifndef PYBIND11_BUILD_ABI -# if defined(__GXX_ABI_VERSION) // Linux/OSX. -# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(__GXX_ABI_VERSION) -# elif defined(_MSC_VER) // See PR #4953. +# if defined(_MSC_VER) // See PR #4953. # if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd. # if (_MSC_VER) / 100 == 19 # define PYBIND11_BUILD_ABI "_md_mscver19" @@ -72,17 +53,35 @@ # error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE." # endif # endif -# elif defined(__NVCOMPILER) // NVHPC (PGI-based). -# define PYBIND11_BUILD_ABI "" // TODO: What should be here, to prevent UB? +# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html +# define PYBIND11_BUILD_ABI \ + "_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION) +# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439. +# if defined(__NVCOMPILER) +// // Assume that NVHPC is in the 1xxx ABI family. +// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do. +// // Please let us know if there is a way to validate the assumption here. +# elif !defined(__GXX_ABI_VERSION) +# error \ + "Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE." +# endif +# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000 +# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE." +# endif +# define PYBIND11_BUILD_ABI \ + "_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \ + _GLIBCXX_USE_CXX11_ABI) # else # error "Unknown platform or compiler: PLEASE REVISE THIS CODE." # endif #endif -#ifndef PYBIND11_INTERNALS_KIND -# define PYBIND11_INTERNALS_KIND "" +// On MSVC, debug and release builds are not ABI-compatible! +#if defined(_MSC_VER) && defined(_DEBUG) +# define PYBIND11_BUILD_TYPE "_debug" +#else +# define PYBIND11_BUILD_TYPE "" #endif #define PYBIND11_PLATFORM_ABI_ID \ - PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \ - PYBIND11_BUILD_TYPE + PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 278f35bba..5fcaf9b9c 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -272,11 +272,11 @@ struct type_info { #define PYBIND11_INTERNALS_ID \ "__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" #define PYBIND11_MODULE_LOCAL_ID \ "__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \ - PYBIND11_PLATFORM_ABI_ID "__" + PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__" /// Each module locally stores a pointer to the `internals` data. The data /// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`. From 5b503f7ec973fcf4ba96a2f522a0692be5c4864f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 Dec 2024 01:42:32 -0800 Subject: [PATCH 2/3] chore(deps): bump actions/attest-build-provenance in the actions group (#5468) Bumps the actions group with 1 update: [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance). Updates `actions/attest-build-provenance` from 2.0.1 to 2.1.0 - [Release notes](https://github.com/actions/attest-build-provenance/releases) - [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md) - [Commits](https://github.com/actions/attest-build-provenance/compare/c4fbc648846ca6f503a13a2281a5e7b98aa57202...7668571508540a607bdfd90a87a560489fe372eb) --- updated-dependencies: - dependency-name: actions/attest-build-provenance dependency-type: direct:production update-type: version-update:semver-minor dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/pip.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index c50f06f94..4291592ea 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -103,7 +103,7 @@ jobs: - uses: actions/download-artifact@v4 - name: Generate artifact attestation for sdist and wheel - uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1 + uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0 with: subject-path: "*/pybind11*" From cf020a1de25861d4b982081890c43e7eab1ff5e9 Mon Sep 17 00:00:00 2001 From: Michael Carlstrom Date: Sat, 21 Dec 2024 16:22:08 -0800 Subject: [PATCH 3/3] feat(types) Allow setting types for attributes (#5460) * init * add comment * style: pre-commit fixes * fix extra ; * Add annotation helper for older python versions * style: pre-commit fixes * test writing __annotations__ to __dict__ * style: pre-commit fixes * revert bac to __annotations__ * use getattr for automatic init * use std::move * update helper * remove stdmove * test isinstance * style: pre-commit fixes * add #if 3.9 * style: pre-commit fixes * use getattr * add dir * use hasattr * try setattr * add c++17 guard * style: pre-commit fixes * add compile guard * style: pre-commit fixes * cleanup * comments and function name cleanup * style: pre-commit fixes * update test case * style: pre-commit fixes * add write * added instance check * style: pre-commit fixes * Test for `__cpp_inline_variables` and use `static_assert()` * Add guard: __annotations__["list_int"] was set already. * Avoid explicit `false` in `static_assert()`. * add redeclaration test * style: pre-commit fixes * add handle support to attr_with_type_hint * store reintpreted_key * style: pre-commit fixes * fix str namespace * fix scope? * string wrap * clang tidy * Swap order of attr_with_type_hint implementation in cast.h (so that the `const char *` overload appears first). * Reuse `const char *` overload from `handle` overload. * Added comment * style: pre-commit fixes * line up comment * fixed spelling error * Add missing period * Introduce `detail::always_false<>` to make the new `static_assert()` more readable. * Copy `attr` comment for `const char *` overload, for consistency. * static_assert(std::false_type::value, ...) * Revert "static_assert(std::false_type::value, ...)" This reverts commit 999b668688b5b965a6f929a5a7f8af5176c8a21d. * Add comment for `always_false` * add test for inspect.get_annotations() * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ralf W. Grosse-Kunstleve --- include/pybind11/cast.h | 25 +++++++++ include/pybind11/detail/common.h | 8 +++ include/pybind11/pytypes.h | 27 ++++++++++ include/pybind11/typing.h | 22 ++++++++ tests/test_pytypes.cpp | 32 ++++++++++++ tests/test_pytypes.py | 90 ++++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 3e15d8d0c..853165a49 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -1366,6 +1366,31 @@ object object_or_cast(T &&o) { return pybind11::cast(std::forward(o)); } +// Declared in pytypes.h: +// Implemented here so that make_caster can be used. +template +template +str_attr_accessor object_api::attr_with_type_hint(const char *key) const { +#if !defined(__cpp_inline_variables) + static_assert(always_false::value, + "C++17 feature __cpp_inline_variables not available: " + "https://en.cppreference.com/w/cpp/language/static#Static_data_members"); +#endif + object ann = annotations(); + if (ann.contains(key)) { + throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already."); + } + ann[key] = make_caster::name.text; + return {derived(), key}; +} + +template +template +obj_attr_accessor object_api::attr_with_type_hint(handle key) const { + (void) attr_with_type_hint(key.cast().c_str()); + return {derived(), reinterpret_borrow(key)}; +} + // Placeholder type for the unneeded (and dead code) static variable in the // PYBIND11_OVERRIDE_OVERRIDE macro struct override_unused {}; diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 78173cad3..c05db0e7d 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -627,6 +627,14 @@ struct instance { static_assert(std::is_standard_layout::value, "Internal error: `pybind11::detail::instance` is not standard layout!"); +// Some older compilers (e.g. gcc 9.4.0) require +// static_assert(always_false::value, "..."); +// instead of +// static_assert(false, "..."); +// to trigger the static_assert() in a template only if it is actually instantiated. +template +struct always_false : std::false_type {}; + /// from __cpp_future__ import (convenient aliases from C++14/17) #if defined(PYBIND11_CPP14) using std::conditional_t; diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 60d51fdcf..92e0a81f4 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -113,6 +113,17 @@ public: /// See above (the only difference is that the key is provided as a string literal) str_attr_accessor attr(const char *key) const; + /** \rst + Similar to the above attr functions with the difference that the templated Type + is used to set the `__annotations__` dict value to the corresponding key. Worth noting + that attr_with_type_hint is implemented in cast.h. + \endrst */ + template + obj_attr_accessor attr_with_type_hint(handle key) const; + /// See above (the only difference is that the key is provided as a string literal) + template + str_attr_accessor attr_with_type_hint(const char *key) const; + /** \rst Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple`` or ``list`` for a function call. Applying another * to the result yields @@ -182,6 +193,9 @@ public: /// Get or set the object's docstring, i.e. ``obj.__doc__``. str_attr_accessor doc() const; + /// Get or set the object's annotations, i.e. ``obj.__annotations__``. + object annotations() const; + /// Return the object's current reference count ssize_t ref_count() const { #ifdef PYPY_VERSION @@ -2558,6 +2572,19 @@ str_attr_accessor object_api::doc() const { return attr("__doc__"); } +template +object object_api::annotations() const { +#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9 + // https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older + if (!hasattr(derived(), "__annotations__")) { + setattr(derived(), "__annotations__", dict()); + } + return attr("__annotations__"); +#else + return getattr(derived(), "__annotations__", dict()); +#endif +} + template handle object_api::get_type() const { return type::handle_of(derived()); diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 405ff8714..005279058 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -82,6 +82,18 @@ class Optional : public object { using object::object; }; +template +class Final : public object { + PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type) + using object::object; +}; + +template +class ClassVar : public object { + PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type) + using object::object; +}; + template class TypeGuard : public bool_ { using bool_::bool_; @@ -251,6 +263,16 @@ struct handle_type_name> { = const_name("Optional[") + as_return_type>::name + const_name("]"); }; +template +struct handle_type_name> { + static constexpr auto name = const_name("Final[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("ClassVar[") + make_caster::name + const_name("]"); +}; + // TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually // the narrower type. diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 1764ccda0..b4fa99192 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -1037,6 +1037,38 @@ TEST_SUBMODULE(pytypes, m) { #else m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false; #endif + +#if defined(__cpp_inline_variables) + // Exercises const char* overload: + m.attr_with_type_hint>("list_int") = py::list(); + // Exercises py::handle overload: + m.attr_with_type_hint>(py::str("set_str")) = py::set(); + + struct Empty {}; + py::class_(m, "EmptyAnnotationClass"); + + struct Static {}; + auto static_class = py::class_(m, "Static"); + static_class.def(py::init()); + static_class.attr_with_type_hint>("x") = 1.0; + static_class.attr_with_type_hint>>( + "dict_str_int") + = py::dict(); + + struct Instance {}; + auto instance = py::class_(m, "Instance", py::dynamic_attr()); + instance.def(py::init()); + instance.attr_with_type_hint("y"); + + m.def("attr_with_type_hint_float_x", + [](py::handle obj) { obj.attr_with_type_hint("x"); }); + + m.attr_with_type_hint>("CONST_INT") = 3; + + m.attr("defined___cpp_inline_variables") = true; +#else + m.attr("defined___cpp_inline_variables") = false; +#endif m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; }); // std::vector m.def("half_of_number_vector", [](const std::vector &x) { diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index b6e64b9bf..448bfa6a8 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -1103,6 +1103,96 @@ def test_dict_ranges(tested_dict, expected): assert m.transform_dict_plus_one(tested_dict) == expected +# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older +def get_annotations_helper(o): + if isinstance(o, type): + return o.__dict__.get("__annotations__", None) + return getattr(o, "__annotations__", None) + + +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +def test_module_attribute_types() -> None: + module_annotations = get_annotations_helper(m) + + assert module_annotations["list_int"] == "list[int]" + assert module_annotations["set_str"] == "set[str]" + + +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +@pytest.mark.skipif( + sys.version_info < (3, 10), + reason="get_annotations function does not exist until Python3.10", +) +def test_get_annotations_compliance() -> None: + from inspect import get_annotations + + module_annotations = get_annotations(m) + + assert module_annotations["list_int"] == "list[int]" + assert module_annotations["set_str"] == "set[str]" + + +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +def test_class_attribute_types() -> None: + empty_annotations = get_annotations_helper(m.EmptyAnnotationClass) + static_annotations = get_annotations_helper(m.Static) + instance_annotations = get_annotations_helper(m.Instance) + + assert empty_annotations is None + assert static_annotations["x"] == "ClassVar[float]" + assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]" + + assert m.Static.x == 1.0 + + m.Static.x = 3.0 + static = m.Static() + assert static.x == 3.0 + + static.dict_str_int["hi"] = 3 + assert m.Static().dict_str_int == {"hi": 3} + + assert instance_annotations["y"] == "float" + instance1 = m.Instance() + instance1.y = 4.0 + + instance2 = m.Instance() + instance2.y = 5.0 + + assert instance1.y != instance2.y + + +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +def test_redeclaration_attr_with_type_hint() -> None: + obj = m.Instance() + m.attr_with_type_hint_float_x(obj) + assert get_annotations_helper(obj)["x"] == "float" + with pytest.raises( + RuntimeError, match=r'^__annotations__\["x"\] was set already\.$' + ): + m.attr_with_type_hint_float_x(obj) + + +@pytest.mark.skipif( + not m.defined___cpp_inline_variables, + reason="C++17 feature __cpp_inline_variables not available.", +) +def test_final_annotation() -> None: + module_annotations = get_annotations_helper(m) + assert module_annotations["CONST_INT"] == "Final[int]" + + def test_arg_return_type_hints(doc): assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float" assert m.half_of_number(2.0) == 1.0