mirror of
https://github.com/pybind/pybind11.git
synced 2026-03-14 20:27:47 +00:00
fix: the types for return_value_policy_override in optional_caster (#3376)
* fix: the types for return_value_policy_override in optional_caster `return_value_policy_override` was not being applied correctly in `optional_caster` in two ways: - The `is_lvalue_reference` condition referenced `T`, which was the `optional<T>` type parameter from the class, when it should have used `T_`, which was the parameter to the `cast` function. `T_` can potentially be a reference type, but `T` will never be. - The type parameter passed to `return_value_policy_override` should be `T::value_type`, not `T`. This matches the way that the other STL container type casters work. The result of these issues was that a method/property definition which used a `reference` or `reference_internal` return value policy would create a Python value that's bound by reference to a temporary C++ object, resulting in undefined behavior. For reasons that I was not able to figure out fully, it seems like this causes problems when using old versions of `boost::optional`, but not with recent versions of `boost::optional` or the `libstdc++` implementation of `std::optional`. The issue (that the override to `return_value_policy::move` is never being applied) is present for all implementations, it just seems like that somehow doesn't result in problems for the some implementation of `optional`. This change includes a regression type with a custom optional-like type which was able to reproduce the issue. Part of the issue with using the wrong types may have stemmed from the type variables `T` and `T_` having very similar names. This also changes the type variables in `optional_caster` to use slightly more descriptive names, which also more closely follow the naming convention used by the other STL casters. Fixes #3330 * Fix clang-tidy complaints * Add missing NOLINT * Apply a couple more fixes * fix: support GCC 4.8 * tests: avoid warning about unknown compiler for compilers missing C++17 * Remove unneeded test module attribute * Change test enum to have more unique int values Co-authored-by: Aaron Gokaslan <skylion.aaron@gmail.com> Co-authored-by: Henry Schreiner <HenrySchreinerIII@gmail.com>
This commit is contained in:
@@ -132,6 +132,10 @@ def test_optional():
|
||||
assert mvalue.initialized
|
||||
assert holder.member_initialized()
|
||||
|
||||
props = m.OptionalProperties()
|
||||
assert int(props.access_by_ref) == 42
|
||||
assert int(props.access_by_copy) == 42
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(m, "has_exp_optional"), reason="no <experimental/optional>"
|
||||
@@ -160,6 +164,69 @@ def test_exp_optional():
|
||||
assert mvalue.initialized
|
||||
assert holder.member_initialized()
|
||||
|
||||
props = m.OptionalExpProperties()
|
||||
assert int(props.access_by_ref) == 42
|
||||
assert int(props.access_by_copy) == 42
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(m, "has_boost_optional"), reason="no <boost/optional>")
|
||||
def test_boost_optional():
|
||||
assert m.double_or_zero_boost(None) == 0
|
||||
assert m.double_or_zero_boost(42) == 84
|
||||
pytest.raises(TypeError, m.double_or_zero_boost, "foo")
|
||||
|
||||
assert m.half_or_none_boost(0) is None
|
||||
assert m.half_or_none_boost(42) == 21
|
||||
pytest.raises(TypeError, m.half_or_none_boost, "foo")
|
||||
|
||||
assert m.test_nullopt_boost() == 42
|
||||
assert m.test_nullopt_boost(None) == 42
|
||||
assert m.test_nullopt_boost(42) == 42
|
||||
assert m.test_nullopt_boost(43) == 43
|
||||
|
||||
assert m.test_no_assign_boost() == 42
|
||||
assert m.test_no_assign_boost(None) == 42
|
||||
assert m.test_no_assign_boost(m.NoAssign(43)) == 43
|
||||
pytest.raises(TypeError, m.test_no_assign_boost, 43)
|
||||
|
||||
holder = m.OptionalBoostHolder()
|
||||
mvalue = holder.member
|
||||
assert mvalue.initialized
|
||||
assert holder.member_initialized()
|
||||
|
||||
props = m.OptionalBoostProperties()
|
||||
assert int(props.access_by_ref) == 42
|
||||
assert int(props.access_by_copy) == 42
|
||||
|
||||
|
||||
def test_reference_sensitive_optional():
|
||||
assert m.double_or_zero_refsensitive(None) == 0
|
||||
assert m.double_or_zero_refsensitive(42) == 84
|
||||
pytest.raises(TypeError, m.double_or_zero_refsensitive, "foo")
|
||||
|
||||
assert m.half_or_none_refsensitive(0) is None
|
||||
assert m.half_or_none_refsensitive(42) == 21
|
||||
pytest.raises(TypeError, m.half_or_none_refsensitive, "foo")
|
||||
|
||||
assert m.test_nullopt_refsensitive() == 42
|
||||
assert m.test_nullopt_refsensitive(None) == 42
|
||||
assert m.test_nullopt_refsensitive(42) == 42
|
||||
assert m.test_nullopt_refsensitive(43) == 43
|
||||
|
||||
assert m.test_no_assign_refsensitive() == 42
|
||||
assert m.test_no_assign_refsensitive(None) == 42
|
||||
assert m.test_no_assign_refsensitive(m.NoAssign(43)) == 43
|
||||
pytest.raises(TypeError, m.test_no_assign_refsensitive, 43)
|
||||
|
||||
holder = m.OptionalRefSensitiveHolder()
|
||||
mvalue = holder.member
|
||||
assert mvalue.initialized
|
||||
assert holder.member_initialized()
|
||||
|
||||
props = m.OptionalRefSensitiveProperties()
|
||||
assert int(props.access_by_ref) == 42
|
||||
assert int(props.access_by_copy) == 42
|
||||
|
||||
|
||||
@pytest.mark.skipif(not hasattr(m, "has_filesystem"), reason="no <filesystem>")
|
||||
def test_fs_path():
|
||||
|
||||
Reference in New Issue
Block a user