Skip to content

Commit fc33860

Browse files
committed
Update to pymetabind 0.3
1 parent 245fa5a commit fc33860

File tree

5 files changed

+189
-99
lines changed

5 files changed

+189
-99
lines changed

include/nanobind/nb_lib.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,13 @@ NB_CORE PyObject *nb_type_new(const type_init_data *c) noexcept;
286286
NB_CORE bool nb_type_get(const std::type_info *t, PyObject *o, uint8_t flags,
287287
cleanup_list *cleanup, void **out) noexcept;
288288

289-
/// Cast a C++ type instance into a Python object
289+
/// Cast a C++ type instance into a Python object.
290+
///
291+
/// Note: If you call this as if casting a shared_ptr (non-null `is_new`, true
292+
/// `allow_foreign`, and `rv_policy::reference`) and you obtain a result with
293+
/// `*is_new == true`, you must call `keep_alive` on the new instance after
294+
/// you get it back. This allows for better interoperability with other
295+
/// frameworks that store a smart pointer inside the instance.
290296
NB_CORE PyObject *nb_type_put(const std::type_info *cpp_type, void *value,
291297
rv_policy rvp, cleanup_list *cleanup,
292298
bool *is_new = nullptr,

src/nb_foreign.cpp

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ static void *nb_foreign_from_python(pymb_binding *binding,
8080
static PyObject *nb_foreign_to_python(pymb_binding *binding,
8181
void *cobj,
8282
enum pymb_rv_policy rvp_,
83-
PyObject *parent) noexcept {
84-
cleanup_list cleanup{parent};
83+
pymb_to_python_feedback *feedback) noexcept {
84+
feedback->relocate = 0; // we don't support relocation
85+
feedback->is_new = 0; // unless overridden below
86+
8587
auto *td = (type_data *) binding->context;
8688
if (td->align == 0) { // enum
8789
int64_t key;
@@ -102,15 +104,25 @@ static PyObject *nb_foreign_to_python(pymb_binding *binding,
102104
return enum_from_cpp(td->type, key, td->size);
103105
}
104106

105-
rv_policy rvp = (rv_policy) rvp_;
106-
if (rvp < rv_policy::take_ownership || rvp > rv_policy::none) {
107-
// Future-proofing in case additional pymb_rv_policies are defined
108-
// later: if we don't recognize this policy, then refuse the cast
109-
// unless a pyobject wrapper already exists.
110-
rvp = rv_policy::none;
107+
rv_policy rvp = rv_policy::none; // conservative default if rvp unrecognized
108+
switch (rvp_) {
109+
case pymb_rv_policy_take_ownership:
110+
case pymb_rv_policy_copy:
111+
case pymb_rv_policy_move:
112+
case pymb_rv_policy_reference:
113+
case pymb_rv_policy_none:
114+
// These have the same values and semantics as our own policies
115+
rvp = (rv_policy) rvp_;
116+
break;
117+
case pymb_rv_policy_share_ownership:
118+
// Shared ownership is always held by keep_alive in nanobind,
119+
// so there's nothing special to do here.
120+
rvp = rv_policy::reference;
121+
break;
111122
}
112-
return nb_type_put(td->type, cobj, rvp, &cleanup,
113-
/* is_new */ nullptr, /* allow_foreign */ false);
123+
return nb_type_put(td->type, cobj, rvp, /* cleanup */ nullptr,
124+
/* is_new */ (bool *) &feedback->is_new,
125+
/* allow_foreign */ false);
114126
}
115127

116128
static int nb_foreign_keep_alive(PyObject *nurse,
@@ -121,10 +133,10 @@ static int nb_foreign_keep_alive(PyObject *nurse,
121133
keep_alive(nurse, payload, (void (*)(void*) noexcept) cb);
122134
else
123135
keep_alive(nurse, (PyObject *) payload);
124-
return 0;
136+
return 1;
125137
} catch (const std::runtime_error& err) {
126-
PyErr_SetString(PyExc_RuntimeError, err.what());
127-
return -1;
138+
PyErr_WriteUnraisable(nurse);
139+
return 0;
128140
}
129141
}
130142

@@ -388,14 +400,14 @@ void nb_type_import_impl(PyObject *pytype, const std::type_info *cpptype) {
388400
register_with_pymetabind(internals);
389401
pymb_framework* foreign_self = internals->foreign_self;
390402
pymb_binding* binding = pymb_get_binding(pytype);
391-
#if defined(Py_LIMITED_API)
392-
str name_py = steal<str>(PyType_GetName((PyTypeObject *) pytype));
403+
str name_py = steal<str>(nb_type_name(pytype));
393404
const char *name = name_py.c_str();
394-
#else
395-
const char *name = ((PyTypeObject *) pytype)->tp_name;
396-
#endif
397405
if (!binding)
398406
raise("'%s' does not define a __pymetabind_binding__", name);
407+
if (binding->pytype != (PyTypeObject *) pytype)
408+
raise("The binding defined by '%s' is for a different type '%s', "
409+
"likely a superclass; import that type instead",
410+
name, steal<str>(nb_type_name((PyObject *) binding->pytype)).c_str());
399411
if (binding->framework == foreign_self)
400412
raise("'%s' is already bound by this nanobind domain", name);
401413
if (!cpptype) {

src/nb_type.cpp

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1797,22 +1797,18 @@ void keep_alive(PyObject *nurse, PyObject *patient) {
17971797
Py_INCREF(patient);
17981798
((nb_inst *) nurse)->clear_keep_alive = true;
17991799
} else {
1800+
#if !defined(NB_DISABLE_INTEROP)
1801+
if (pymb_binding *binding = pymb_get_binding(nurse);
1802+
binding && binding->framework->keep_alive(nurse, patient, nullptr))
1803+
return;
1804+
#endif
18001805
PyObject *callback =
18011806
PyCFunction_New(&keep_alive_callback_def, patient);
18021807

18031808
PyObject *weakref = PyWeakref_NewRef(nurse, callback);
18041809
if (!weakref) {
18051810
Py_DECREF(callback);
18061811
PyErr_Clear();
1807-
#if !defined(NB_DISABLE_INTEROP)
1808-
if (pymb_binding *binding = pymb_get_binding(nurse)) {
1809-
// Try a foreign framework's keep_alive as a last resort
1810-
if (binding->framework->keep_alive(nurse, patient,
1811-
nullptr) == 0)
1812-
return;
1813-
raise_python_error();
1814-
}
1815-
#endif
18161812
raise("nanobind::detail::keep_alive(): could not create a weak "
18171813
"reference! Likely, the 'nurse' argument you specified is not "
18181814
"a weak-referenceable type!");
@@ -1830,7 +1826,8 @@ void keep_alive(PyObject *nurse, void *payload,
18301826
void (*callback)(void *) noexcept) noexcept {
18311827
check(nurse, "nanobind::detail::keep_alive(): 'nurse' is undefined!");
18321828

1833-
if (nb_type_check((PyObject *) Py_TYPE(nurse))) {
1829+
PyObject *nurse_ty = (PyObject *) Py_TYPE(nurse);
1830+
if (nb_type_check(nurse_ty)) {
18341831
#if defined(NB_FREE_THREADED)
18351832
nb_shard &shard = internals->shard(inst_ptr((nb_inst *) nurse));
18361833
lock_shard guard(shard);
@@ -1850,6 +1847,11 @@ void keep_alive(PyObject *nurse, void *payload,
18501847

18511848
((nb_inst *) nurse)->clear_keep_alive = true;
18521849
} else {
1850+
#if !defined(NB_DISABLE_INTEROP)
1851+
if (pymb_binding *binding = pymb_get_binding(nurse_ty);
1852+
binding && binding->framework->keep_alive(nurse, payload, callback))
1853+
return;
1854+
#endif
18531855
PyObject *patient = capsule_new(payload, nullptr, callback);
18541856
keep_alive(nurse, patient);
18551857
Py_DECREF(patient);
@@ -1957,36 +1959,64 @@ PyObject *nb_type_put_foreign(nb_internals *internals_,
19571959
bool *is_new) noexcept {
19581960
struct capture {
19591961
void *value;
1960-
rv_policy rvp;
1961-
PyObject *parent;
1962-
bool check_new;
1963-
bool is_new = false;
1964-
} cap{value, rvp, cleanup ? cleanup->self() : nullptr, bool(is_new)};
1962+
pymb_rv_policy rvp = pymb_rv_policy_none; // conservative default
1963+
struct pymb_to_python_feedback feedback{};
1964+
struct pymb_framework *used_framework = nullptr;
1965+
} cap{value};
1966+
1967+
switch (rvp) {
1968+
case rv_policy::reference_internal:
1969+
if (!cleanup || !cleanup->self())
1970+
return nullptr;
1971+
cap.rvp = pymb_rv_policy_share_ownership;
1972+
break;
1973+
case rv_policy::reference:
1974+
if (is_new) {
1975+
cap.rvp = pymb_rv_policy_share_ownership;
1976+
break;
1977+
}
1978+
[[fallthrough]];
1979+
case rv_policy::take_ownership:
1980+
case rv_policy::copy:
1981+
case rv_policy::move:
1982+
case rv_policy::none:
1983+
cap.rvp = (pymb_rv_policy) rvp;
1984+
break;
1985+
case rv_policy::automatic:
1986+
case rv_policy::automatic_reference:
1987+
check(false,
1988+
"nb_type_put_foreign(): automatic rvp should have been "
1989+
"converted to a different one before reaching here!");
1990+
break;
1991+
}
19651992

19661993
auto attempt = +[](void *closure, pymb_binding *binding) -> void* {
19671994
capture &cap = *(capture *) closure;
1968-
if (cap.check_new || cap.rvp == rv_policy::none) {
1969-
PyObject* existing = binding->framework->to_python(
1970-
binding, cap.value, pymb_rv_policy_none, nullptr);
1971-
if (existing || cap.rvp == rv_policy::none) {
1972-
cap.is_new = false;
1973-
return existing;
1974-
}
1975-
cap.is_new = true;
1976-
}
1995+
cap.used_framework = binding->framework;
19771996
return binding->framework->to_python(
1978-
binding, cap.value, (pymb_rv_policy) (uint8_t) cap.rvp,
1979-
cap.parent);
1997+
binding, cap.value, cap.rvp, &cap.feedback);
19801998
};
19811999

1982-
void *result = nullptr;
2000+
void *result_v = nullptr;
19832001
if (cpp_type_p && cpp_type_p != cpp_type)
1984-
result = nb_type_try_foreign(internals_, cpp_type_p, attempt, &cap);
1985-
if (!result)
1986-
result = nb_type_try_foreign(internals_, cpp_type, attempt, &cap);
2002+
result_v = nb_type_try_foreign(internals_, cpp_type_p, attempt, &cap);
2003+
if (!result_v)
2004+
result_v = nb_type_try_foreign(internals_, cpp_type, attempt, &cap);
2005+
2006+
PyObject *result = (PyObject *) result_v;
19872007
if (is_new)
1988-
*is_new = cap.is_new;
1989-
return (PyObject *) result;
2008+
*is_new = cap.feedback.is_new;
2009+
if (result && rvp == rv_policy::reference_internal && cap.feedback.is_new &&
2010+
!cap.used_framework->keep_alive(result, cleanup->self(), nullptr)) {
2011+
try {
2012+
keep_alive(result, cleanup->self());
2013+
} catch (python_error& exc) {
2014+
exc.restore();
2015+
Py_DECREF(result);
2016+
return nullptr;
2017+
}
2018+
}
2019+
return result;
19902020
}
19912021
#endif
19922022

0 commit comments

Comments
 (0)