Skip to content

Commit 85b385d

Browse files
Merge branch 'pybind:master' into master
2 parents a4fec42 + cf020a1 commit 85b385d

File tree

9 files changed

+247
-44
lines changed

9 files changed

+247
-44
lines changed

.github/workflows/pip.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ jobs:
103103
- uses: actions/download-artifact@v4
104104

105105
- name: Generate artifact attestation for sdist and wheel
106-
uses: actions/attest-build-provenance@c4fbc648846ca6f503a13a2281a5e7b98aa57202 # v2.0.1
106+
uses: actions/attest-build-provenance@7668571508540a607bdfd90a87a560489fe372eb # v2.1.0
107107
with:
108108
subject-path: "*/pybind11*"
109109

include/pybind11/cast.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,31 @@ object object_or_cast(T &&o) {
13661366
return pybind11::cast(std::forward<T>(o));
13671367
}
13681368

1369+
// Declared in pytypes.h:
1370+
// Implemented here so that make_caster<T> can be used.
1371+
template <typename D>
1372+
template <typename T>
1373+
str_attr_accessor object_api<D>::attr_with_type_hint(const char *key) const {
1374+
#if !defined(__cpp_inline_variables)
1375+
static_assert(always_false<T>::value,
1376+
"C++17 feature __cpp_inline_variables not available: "
1377+
"https://en.cppreference.com/w/cpp/language/static#Static_data_members");
1378+
#endif
1379+
object ann = annotations();
1380+
if (ann.contains(key)) {
1381+
throw std::runtime_error("__annotations__[\"" + std::string(key) + "\"] was set already.");
1382+
}
1383+
ann[key] = make_caster<T>::name.text;
1384+
return {derived(), key};
1385+
}
1386+
1387+
template <typename D>
1388+
template <typename T>
1389+
obj_attr_accessor object_api<D>::attr_with_type_hint(handle key) const {
1390+
(void) attr_with_type_hint<T>(key.cast<std::string>().c_str());
1391+
return {derived(), reinterpret_borrow<object>(key)};
1392+
}
1393+
13691394
// Placeholder type for the unneeded (and dead code) static variable in the
13701395
// PYBIND11_OVERRIDE_OVERRIDE macro
13711396
struct override_unused {};

include/pybind11/conduit/pybind11_platform_abi_id.h

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -12,51 +12,32 @@
1212
#define PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x) #x
1313
#define PYBIND11_PLATFORM_ABI_ID_TOSTRING(x) PYBIND11_PLATFORM_ABI_ID_STRINGIFY(x)
1414

15-
// On MSVC, debug and release builds are not ABI-compatible!
16-
#if defined(_MSC_VER) && defined(_DEBUG)
17-
# define PYBIND11_BUILD_TYPE "_debug"
15+
#ifdef PYBIND11_COMPILER_TYPE
16+
// // To maintain backward compatibility (see PR #5439).
17+
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE ""
1818
#else
19-
# define PYBIND11_BUILD_TYPE ""
20-
#endif
21-
22-
// Let's assume that different compilers are ABI-incompatible.
23-
// A user can manually set this string if they know their
24-
// compiler is compatible.
25-
#ifndef PYBIND11_COMPILER_TYPE
26-
# if defined(_MSC_VER)
27-
# define PYBIND11_COMPILER_TYPE "_msvc"
28-
# elif defined(__INTEL_COMPILER)
29-
# define PYBIND11_COMPILER_TYPE "_icc"
30-
# elif defined(__clang__)
31-
# define PYBIND11_COMPILER_TYPE "_clang"
32-
# elif defined(__PGI)
33-
# define PYBIND11_COMPILER_TYPE "_pgi"
34-
# elif defined(__MINGW32__)
35-
# define PYBIND11_COMPILER_TYPE "_mingw"
19+
# define PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE "_"
20+
# if defined(__MINGW32__)
21+
# define PYBIND11_COMPILER_TYPE "mingw"
3622
# elif defined(__CYGWIN__)
37-
# define PYBIND11_COMPILER_TYPE "_gcc_cygwin"
38-
# elif defined(__GNUC__)
39-
# define PYBIND11_COMPILER_TYPE "_gcc"
23+
# define PYBIND11_COMPILER_TYPE "gcc_cygwin"
24+
# elif defined(_MSC_VER)
25+
# define PYBIND11_COMPILER_TYPE "msvc"
26+
# elif defined(__clang__) || defined(__GNUC__)
27+
# define PYBIND11_COMPILER_TYPE "system" // Assumed compatible with system compiler.
4028
# else
41-
# define PYBIND11_COMPILER_TYPE "_unknown"
29+
# error "Unknown PYBIND11_COMPILER_TYPE: PLEASE REVISE THIS CODE."
4230
# endif
4331
#endif
4432

45-
// Also standard libs
33+
// PR #5439 made this macro obsolete. However, there are many manipulations of this macro in the
34+
// wild. Therefore, to maintain backward compatibility, it is kept around.
4635
#ifndef PYBIND11_STDLIB
47-
# if defined(_LIBCPP_VERSION)
48-
# define PYBIND11_STDLIB "_libcpp"
49-
# elif defined(__GLIBCXX__) || defined(__GLIBCPP__)
50-
# define PYBIND11_STDLIB "_libstdcpp"
51-
# else
52-
# define PYBIND11_STDLIB ""
53-
# endif
36+
# define PYBIND11_STDLIB ""
5437
#endif
5538

5639
#ifndef PYBIND11_BUILD_ABI
57-
# if defined(__GXX_ABI_VERSION) // Linux/OSX.
58-
# define PYBIND11_BUILD_ABI "_cxxabi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(__GXX_ABI_VERSION)
59-
# elif defined(_MSC_VER) // See PR #4953.
40+
# if defined(_MSC_VER) // See PR #4953.
6041
# if defined(_MT) && defined(_DLL) // Corresponding to CL command line options /MD or /MDd.
6142
# if (_MSC_VER) / 100 == 19
6243
# define PYBIND11_BUILD_ABI "_md_mscver19"
@@ -72,17 +53,35 @@
7253
# error "Unknown major version for MSC_VER: PLEASE REVISE THIS CODE."
7354
# endif
7455
# endif
75-
# elif defined(__NVCOMPILER) // NVHPC (PGI-based).
76-
# define PYBIND11_BUILD_ABI "" // TODO: What should be here, to prevent UB?
56+
# elif defined(_LIBCPP_ABI_VERSION) // https://libcxx.llvm.org/DesignDocs/ABIVersioning.html
57+
# define PYBIND11_BUILD_ABI \
58+
"_libcpp_abi" PYBIND11_PLATFORM_ABI_ID_TOSTRING(_LIBCPP_ABI_VERSION)
59+
# elif defined(_GLIBCXX_USE_CXX11_ABI) // See PR #5439.
60+
# if defined(__NVCOMPILER)
61+
// // Assume that NVHPC is in the 1xxx ABI family.
62+
// // THIS ASSUMPTION IS NOT FUTURE PROOF but apparently the best we can do.
63+
// // Please let us know if there is a way to validate the assumption here.
64+
# elif !defined(__GXX_ABI_VERSION)
65+
# error \
66+
"Unknown platform or compiler (_GLIBCXX_USE_CXX11_ABI): PLEASE REVISE THIS CODE."
67+
# endif
68+
# if defined(__GXX_ABI_VERSION) && __GXX_ABI_VERSION < 1002 || __GXX_ABI_VERSION >= 2000
69+
# error "Unknown platform or compiler (__GXX_ABI_VERSION): PLEASE REVISE THIS CODE."
70+
# endif
71+
# define PYBIND11_BUILD_ABI \
72+
"_libstdcpp_gxx_abi_1xxx_use_cxx11_abi_" PYBIND11_PLATFORM_ABI_ID_TOSTRING( \
73+
_GLIBCXX_USE_CXX11_ABI)
7774
# else
7875
# error "Unknown platform or compiler: PLEASE REVISE THIS CODE."
7976
# endif
8077
#endif
8178

82-
#ifndef PYBIND11_INTERNALS_KIND
83-
# define PYBIND11_INTERNALS_KIND ""
79+
// On MSVC, debug and release builds are not ABI-compatible!
80+
#if defined(_MSC_VER) && defined(_DEBUG)
81+
# define PYBIND11_BUILD_TYPE "_debug"
82+
#else
83+
# define PYBIND11_BUILD_TYPE ""
8484
#endif
8585

8686
#define PYBIND11_PLATFORM_ABI_ID \
87-
PYBIND11_INTERNALS_KIND PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI \
88-
PYBIND11_BUILD_TYPE
87+
PYBIND11_COMPILER_TYPE PYBIND11_STDLIB PYBIND11_BUILD_ABI PYBIND11_BUILD_TYPE

include/pybind11/detail/common.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,14 @@ struct instance {
627627
static_assert(std::is_standard_layout<instance>::value,
628628
"Internal error: `pybind11::detail::instance` is not standard layout!");
629629

630+
// Some older compilers (e.g. gcc 9.4.0) require
631+
// static_assert(always_false<T>::value, "...");
632+
// instead of
633+
// static_assert(false, "...");
634+
// to trigger the static_assert() in a template only if it is actually instantiated.
635+
template <typename>
636+
struct always_false : std::false_type {};
637+
630638
/// from __cpp_future__ import (convenient aliases from C++14/17)
631639
#if defined(PYBIND11_CPP14)
632640
using std::conditional_t;

include/pybind11/detail/internals.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,11 @@ struct type_info {
272272

273273
#define PYBIND11_INTERNALS_ID \
274274
"__pybind11_internals_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
275-
PYBIND11_PLATFORM_ABI_ID "__"
275+
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
276276

277277
#define PYBIND11_MODULE_LOCAL_ID \
278278
"__pybind11_module_local_v" PYBIND11_TOSTRING(PYBIND11_INTERNALS_VERSION) \
279-
PYBIND11_PLATFORM_ABI_ID "__"
279+
PYBIND11_COMPILER_TYPE_LEADING_UNDERSCORE PYBIND11_PLATFORM_ABI_ID "__"
280280

281281
/// Each module locally stores a pointer to the `internals` data. The data
282282
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.

include/pybind11/pytypes.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ class object_api : public pyobject_tag {
113113
/// See above (the only difference is that the key is provided as a string literal)
114114
str_attr_accessor attr(const char *key) const;
115115

116+
/** \rst
117+
Similar to the above attr functions with the difference that the templated Type
118+
is used to set the `__annotations__` dict value to the corresponding key. Worth noting
119+
that attr_with_type_hint is implemented in cast.h.
120+
\endrst */
121+
template <typename T>
122+
obj_attr_accessor attr_with_type_hint(handle key) const;
123+
/// See above (the only difference is that the key is provided as a string literal)
124+
template <typename T>
125+
str_attr_accessor attr_with_type_hint(const char *key) const;
126+
116127
/** \rst
117128
Matches * unpacking in Python, e.g. to unpack arguments out of a ``tuple``
118129
or ``list`` for a function call. Applying another * to the result yields
@@ -182,6 +193,9 @@ class object_api : public pyobject_tag {
182193
/// Get or set the object's docstring, i.e. ``obj.__doc__``.
183194
str_attr_accessor doc() const;
184195

196+
/// Get or set the object's annotations, i.e. ``obj.__annotations__``.
197+
object annotations() const;
198+
185199
/// Return the object's current reference count
186200
ssize_t ref_count() const {
187201
#ifdef PYPY_VERSION
@@ -2558,6 +2572,19 @@ str_attr_accessor object_api<D>::doc() const {
25582572
return attr("__doc__");
25592573
}
25602574

2575+
template <typename D>
2576+
object object_api<D>::annotations() const {
2577+
#if PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION <= 9
2578+
// https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
2579+
if (!hasattr(derived(), "__annotations__")) {
2580+
setattr(derived(), "__annotations__", dict());
2581+
}
2582+
return attr("__annotations__");
2583+
#else
2584+
return getattr(derived(), "__annotations__", dict());
2585+
#endif
2586+
}
2587+
25612588
template <typename D>
25622589
handle object_api<D>::get_type() const {
25632590
return type::handle_of(derived());

include/pybind11/typing.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ class Optional : public object {
8282
using object::object;
8383
};
8484

85+
template <typename T>
86+
class Final : public object {
87+
PYBIND11_OBJECT_DEFAULT(Final, object, PyObject_Type)
88+
using object::object;
89+
};
90+
91+
template <typename T>
92+
class ClassVar : public object {
93+
PYBIND11_OBJECT_DEFAULT(ClassVar, object, PyObject_Type)
94+
using object::object;
95+
};
96+
8597
template <typename T>
8698
class TypeGuard : public bool_ {
8799
using bool_::bool_;
@@ -251,6 +263,16 @@ struct handle_type_name<typing::Optional<T>> {
251263
= const_name("Optional[") + as_return_type<make_caster<T>>::name + const_name("]");
252264
};
253265

266+
template <typename T>
267+
struct handle_type_name<typing::Final<T>> {
268+
static constexpr auto name = const_name("Final[") + make_caster<T>::name + const_name("]");
269+
};
270+
271+
template <typename T>
272+
struct handle_type_name<typing::ClassVar<T>> {
273+
static constexpr auto name = const_name("ClassVar[") + make_caster<T>::name + const_name("]");
274+
};
275+
254276
// TypeGuard and TypeIs use as_return_type to use the return type if available, which is usually
255277
// the narrower type.
256278

tests/test_pytypes.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,38 @@ TEST_SUBMODULE(pytypes, m) {
10371037
#else
10381038
m.attr("defined_PYBIND11_TEST_PYTYPES_HAS_RANGES") = false;
10391039
#endif
1040+
1041+
#if defined(__cpp_inline_variables)
1042+
// Exercises const char* overload:
1043+
m.attr_with_type_hint<py::typing::List<int>>("list_int") = py::list();
1044+
// Exercises py::handle overload:
1045+
m.attr_with_type_hint<py::typing::Set<py::str>>(py::str("set_str")) = py::set();
1046+
1047+
struct Empty {};
1048+
py::class_<Empty>(m, "EmptyAnnotationClass");
1049+
1050+
struct Static {};
1051+
auto static_class = py::class_<Static>(m, "Static");
1052+
static_class.def(py::init());
1053+
static_class.attr_with_type_hint<py::typing::ClassVar<float>>("x") = 1.0;
1054+
static_class.attr_with_type_hint<py::typing::ClassVar<py::typing::Dict<py::str, int>>>(
1055+
"dict_str_int")
1056+
= py::dict();
1057+
1058+
struct Instance {};
1059+
auto instance = py::class_<Instance>(m, "Instance", py::dynamic_attr());
1060+
instance.def(py::init());
1061+
instance.attr_with_type_hint<float>("y");
1062+
1063+
m.def("attr_with_type_hint_float_x",
1064+
[](py::handle obj) { obj.attr_with_type_hint<float>("x"); });
1065+
1066+
m.attr_with_type_hint<py::typing::Final<int>>("CONST_INT") = 3;
1067+
1068+
m.attr("defined___cpp_inline_variables") = true;
1069+
#else
1070+
m.attr("defined___cpp_inline_variables") = false;
1071+
#endif
10401072
m.def("half_of_number", [](const RealNumber &x) { return RealNumber{x.value / 2}; });
10411073
// std::vector<T>
10421074
m.def("half_of_number_vector", [](const std::vector<RealNumber> &x) {

tests/test_pytypes.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,96 @@ def test_dict_ranges(tested_dict, expected):
11031103
assert m.transform_dict_plus_one(tested_dict) == expected
11041104

11051105

1106+
# https://docs.python.org/3/howto/annotations.html#accessing-the-annotations-dict-of-an-object-in-python-3-9-and-older
1107+
def get_annotations_helper(o):
1108+
if isinstance(o, type):
1109+
return o.__dict__.get("__annotations__", None)
1110+
return getattr(o, "__annotations__", None)
1111+
1112+
1113+
@pytest.mark.skipif(
1114+
not m.defined___cpp_inline_variables,
1115+
reason="C++17 feature __cpp_inline_variables not available.",
1116+
)
1117+
def test_module_attribute_types() -> None:
1118+
module_annotations = get_annotations_helper(m)
1119+
1120+
assert module_annotations["list_int"] == "list[int]"
1121+
assert module_annotations["set_str"] == "set[str]"
1122+
1123+
1124+
@pytest.mark.skipif(
1125+
not m.defined___cpp_inline_variables,
1126+
reason="C++17 feature __cpp_inline_variables not available.",
1127+
)
1128+
@pytest.mark.skipif(
1129+
sys.version_info < (3, 10),
1130+
reason="get_annotations function does not exist until Python3.10",
1131+
)
1132+
def test_get_annotations_compliance() -> None:
1133+
from inspect import get_annotations
1134+
1135+
module_annotations = get_annotations(m)
1136+
1137+
assert module_annotations["list_int"] == "list[int]"
1138+
assert module_annotations["set_str"] == "set[str]"
1139+
1140+
1141+
@pytest.mark.skipif(
1142+
not m.defined___cpp_inline_variables,
1143+
reason="C++17 feature __cpp_inline_variables not available.",
1144+
)
1145+
def test_class_attribute_types() -> None:
1146+
empty_annotations = get_annotations_helper(m.EmptyAnnotationClass)
1147+
static_annotations = get_annotations_helper(m.Static)
1148+
instance_annotations = get_annotations_helper(m.Instance)
1149+
1150+
assert empty_annotations is None
1151+
assert static_annotations["x"] == "ClassVar[float]"
1152+
assert static_annotations["dict_str_int"] == "ClassVar[dict[str, int]]"
1153+
1154+
assert m.Static.x == 1.0
1155+
1156+
m.Static.x = 3.0
1157+
static = m.Static()
1158+
assert static.x == 3.0
1159+
1160+
static.dict_str_int["hi"] = 3
1161+
assert m.Static().dict_str_int == {"hi": 3}
1162+
1163+
assert instance_annotations["y"] == "float"
1164+
instance1 = m.Instance()
1165+
instance1.y = 4.0
1166+
1167+
instance2 = m.Instance()
1168+
instance2.y = 5.0
1169+
1170+
assert instance1.y != instance2.y
1171+
1172+
1173+
@pytest.mark.skipif(
1174+
not m.defined___cpp_inline_variables,
1175+
reason="C++17 feature __cpp_inline_variables not available.",
1176+
)
1177+
def test_redeclaration_attr_with_type_hint() -> None:
1178+
obj = m.Instance()
1179+
m.attr_with_type_hint_float_x(obj)
1180+
assert get_annotations_helper(obj)["x"] == "float"
1181+
with pytest.raises(
1182+
RuntimeError, match=r'^__annotations__\["x"\] was set already\.$'
1183+
):
1184+
m.attr_with_type_hint_float_x(obj)
1185+
1186+
1187+
@pytest.mark.skipif(
1188+
not m.defined___cpp_inline_variables,
1189+
reason="C++17 feature __cpp_inline_variables not available.",
1190+
)
1191+
def test_final_annotation() -> None:
1192+
module_annotations = get_annotations_helper(m)
1193+
assert module_annotations["CONST_INT"] == "Final[int]"
1194+
1195+
11061196
def test_arg_return_type_hints(doc):
11071197
assert doc(m.half_of_number) == "half_of_number(arg0: Union[float, int]) -> float"
11081198
assert m.half_of_number(2.0) == 1.0

0 commit comments

Comments
 (0)