Use static_assert() !std::is_pointer<> to replace runtime guards.

This commit is contained in:
Ralf W. Grosse-Kunstleve 2023-05-18 08:37:03 -07:00
parent 20b9baf270
commit 0640eb3359
7 changed files with 21 additions and 69 deletions

View File

@ -1034,6 +1034,11 @@ struct format_descriptor<
static std::string format() { return std::string(1, c); }
};
// Common message for `static_assert()`s, which are useful to easily preempt much less obvious
// errors in code that does not support `format_descriptor<PyObject *>`.
#define PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED \
"Pointer types (in particular `PyObject *`) are not supported."
PYBIND11_NAMESPACE_BEGIN(detail)
// Returns the index of the given type in the type char array below, and in the list in numpy.h
// The order here is: bool; 8 ints ((signed,unsigned)x(8,16,32,64)bits); float,double,long double;

View File

@ -287,14 +287,11 @@ handle eigen_encapsulate(Type *src) {
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_dense_plain<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using props = EigenProps<Type>;
bool load(handle src, bool convert) {
// dtype=object is not supported. See #1152 & #2259 for related experiments.
if (is_same_ignoring_cvref<Scalar, PyObject *>::value) {
return false;
}
// If we're in no-convert mode, only load if given an array of the correct type
if (!convert && !isinstance<array_t<Scalar>>(src)) {
return false;
@ -410,6 +407,9 @@ private:
// Base class for casting reference/map/block/etc. objects back to python.
template <typename MapType>
struct eigen_map_caster {
static_assert(!std::is_pointer<typename MapType::Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
private:
using props = EigenProps<MapType>;
@ -462,6 +462,8 @@ private:
using Type = Eigen::Ref<PlainObjectType, 0, StrideType>;
using props = EigenProps<Type>;
using Scalar = typename props::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::Map<PlainObjectType, 0, StrideType>;
using Array
= array_t<Scalar,
@ -485,11 +487,6 @@ private:
public:
bool load(handle src, bool convert) {
// dtype=object is not supported. See #1152 & #2259 for related experiments.
if (is_same_ignoring_cvref<Scalar, PyObject *>::value) {
return false;
}
// First check whether what we have is already an array of the right type. If not, we
// can't avoid a copy (because the copy is also going to do type conversion).
bool need_copy = !isinstance<Array>(src);
@ -614,6 +611,9 @@ private:
// regular Eigen::Matrix, then casting that.
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_other<Type>::value>> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
protected:
using Matrix
= Eigen::Matrix<typename Type::Scalar, Type::RowsAtCompileTime, Type::ColsAtCompileTime>;
@ -642,16 +642,13 @@ public:
template <typename Type>
struct type_caster<Type, enable_if_t<is_eigen_sparse<Type>::value>> {
using Scalar = typename Type::Scalar;
static_assert(!std::is_pointer<Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using StorageIndex = remove_reference_t<decltype(*std::declval<Type>().outerIndexPtr())>;
using Index = typename Type::Index;
static constexpr bool rowMajor = Type::IsRowMajor;
bool load(handle src, bool) {
// dtype=object is not supported. See #1152 & #2259 for related experiments.
if (is_same_ignoring_cvref<Scalar, PyObject *>::value) {
return false;
}
if (!src) {
return false;
}

View File

@ -164,16 +164,13 @@ PYBIND11_WARNING_POP
template <typename Type>
struct type_caster<Type, typename eigen_tensor_helper<Type>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using Helper = eigen_tensor_helper<Type>;
static constexpr auto temp_name = get_tensor_descriptor<Type, false>::value;
PYBIND11_TYPE_CASTER(Type, temp_name);
bool load(handle src, bool convert) {
// dtype=object is not supported. See #1152 & #2259 for related experiments.
if (is_same_ignoring_cvref<typename Type::Scalar, PyObject *>::value) {
return false;
}
if (!convert) {
if (!isinstance<array>(src)) {
return false;
@ -364,15 +361,12 @@ struct get_storage_pointer_type<MapType, void_t<typename MapType::PointerArgType
template <typename Type, int Options>
struct type_caster<Eigen::TensorMap<Type, Options>,
typename eigen_tensor_helper<remove_cv_t<Type>>::ValidType> {
static_assert(!std::is_pointer<typename Type::Scalar>::value,
PYBIND11_MESSAGE_POINTER_TYPES_ARE_NOT_SUPPORTED);
using MapType = Eigen::TensorMap<Type, Options>;
using Helper = eigen_tensor_helper<remove_cv_t<Type>>;
bool load(handle src, bool /*convert*/) {
// dtype=object is not supported. See #1152 & #2259 for related experiments.
if (is_same_ignoring_cvref<typename Type::Scalar, PyObject *>::value) {
return false;
}
// Note that we have a lot more checks here as we want to make sure to avoid copies
if (!isinstance<array>(src)) {
return false;

View File

@ -425,10 +425,4 @@ TEST_SUBMODULE(eigen_matrix, m) {
py::module_::import("numpy").attr("ones")(10);
return v[0](5);
});
m.def("pass_eigen_matrix_dtype_object",
[](const Eigen::Matrix<PyObject *, Eigen::Dynamic, Eigen::Dynamic> &) {});
m.def("pass_eigen_ref_matrix_dtype_object",
[](const Eigen::Ref<Eigen::Matrix<PyObject *, Eigen::Dynamic, Eigen::Dynamic>> &) {});
m.def("pass_eigen_sparse_matrix_dtype_object", [](const Eigen::SparseMatrix<PyObject *> &) {});
}

View File

@ -805,20 +805,3 @@ def test_custom_operator_new():
o = m.CustomOperatorNew()
np.testing.assert_allclose(o.a, 0.0)
np.testing.assert_allclose(o.b.diagonal(), 1.0)
@pytest.mark.parametrize(
"pass_eigen_type_dtype_object",
[
m.pass_eigen_matrix_dtype_object,
m.pass_eigen_ref_matrix_dtype_object,
m.pass_eigen_sparse_matrix_dtype_object,
],
)
def test_pass_array_with_dtype_object(pass_eigen_type_dtype_object):
# Only the dtype matters (not shape etc.): dtype=object is (should be) the
# first check in the type_caster load() implementations.
obj = np.array([], dtype=object)
with pytest.raises(TypeError) as excinfo:
pass_eigen_type_dtype_object(obj)
assert "incompatible function arguments" in str(excinfo.value)

View File

@ -320,10 +320,6 @@ void init_tensor_module(pybind11::module &m) {
"round_trip_rank_0_view",
[](Eigen::TensorMap<Eigen::Tensor<double, 0, Options>> &tensor) { return tensor; },
py::return_value_policy::reference);
m.def("pass_eigen_tensor_dtype_object", [](const Eigen::Tensor<PyObject *, 0, Options> &) {});
m.def("pass_eigen_tensor_map_dtype_object",
[](Eigen::TensorMap<Eigen::Tensor<PyObject *, 0, Options>> &) {});
}
void test_module(py::module_ &m) {

View File

@ -286,20 +286,3 @@ def test_doc_string(m, doc):
f"round_trip_const_view_tensor(arg0: numpy.ndarray[numpy.float64[?, ?, ?], {order_flag}])"
" -> numpy.ndarray[numpy.float64[?, ?, ?]]"
)
@pytest.mark.parametrize("m", submodules)
@pytest.mark.parametrize(
"func_name",
[
"pass_eigen_tensor_dtype_object",
"pass_eigen_tensor_map_dtype_object",
],
)
def test_pass_array_with_dtype_object(m, func_name):
# Only the dtype matters (not shape etc.): dtype=object is (should be) the
# first check in the type_caster load() implementations.
obj = np.array([], dtype=object)
with pytest.raises(TypeError) as excinfo:
getattr(m, func_name)(obj)
assert "incompatible function arguments" in str(excinfo.value)