Fix char & arguments being non-bindable

This changes the caster to return a reference to a (new) local `CharT`
type caster member so that binding lvalue-reference char arguments
works (currently it results in a compilation failure).

Fixes #1116
This commit is contained in:
Jason Rhinelander 2017-10-06 11:50:10 -03:00
parent 04b41f0304
commit 1b08df5872
3 changed files with 14 additions and 6 deletions

View File

@ -1216,6 +1216,7 @@ template <typename CharT> struct type_caster<CharT, enable_if_t<is_std_char_type
using StringCaster = type_caster<StringType>; using StringCaster = type_caster<StringType>;
StringCaster str_caster; StringCaster str_caster;
bool none = false; bool none = false;
CharT one_char = 0;
public: public:
bool load(handle src, bool convert) { bool load(handle src, bool convert) {
if (!src) return false; if (!src) return false;
@ -1243,7 +1244,7 @@ public:
} }
operator CharT*() { return none ? nullptr : const_cast<CharT *>(static_cast<StringType &>(str_caster).c_str()); } operator CharT*() { return none ? nullptr : const_cast<CharT *>(static_cast<StringType &>(str_caster).c_str()); }
operator CharT() { operator CharT&() {
if (none) if (none)
throw value_error("Cannot convert None to a character"); throw value_error("Cannot convert None to a character");
@ -1267,7 +1268,8 @@ public:
if (char0_bytes == str_len) { if (char0_bytes == str_len) {
// If we have a 128-255 value, we can decode it into a single char: // If we have a 128-255 value, we can decode it into a single char:
if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx if (char0_bytes == 2 && (v0 & 0xFC) == 0xC0) { // 0x110000xx 0x10xxxxxx
return static_cast<CharT>(((v0 & 3) << 6) + (static_cast<unsigned char>(value[1]) & 0x3F)); one_char = static_cast<CharT>(((v0 & 3) << 6) + (static_cast<unsigned char>(value[1]) & 0x3F));
return one_char;
} }
// Otherwise we have a single character, but it's > U+00FF // Otherwise we have a single character, but it's > U+00FF
throw value_error("Character code point not in range(0x100)"); throw value_error("Character code point not in range(0x100)");
@ -1278,19 +1280,20 @@ public:
// surrogate pair with total length 2 instantly indicates a range error (but not a "your // surrogate pair with total length 2 instantly indicates a range error (but not a "your
// string was too long" error). // string was too long" error).
else if (StringCaster::UTF_N == 16 && str_len == 2) { else if (StringCaster::UTF_N == 16 && str_len == 2) {
char16_t v0 = static_cast<char16_t>(value[0]); one_char = static_cast<CharT>(value[0]);
if (v0 >= 0xD800 && v0 < 0xE000) if (one_char >= 0xD800 && one_char < 0xE000)
throw value_error("Character code point not in range(0x10000)"); throw value_error("Character code point not in range(0x10000)");
} }
if (str_len != 1) if (str_len != 1)
throw value_error("Expected a character, but multi-character string found"); throw value_error("Expected a character, but multi-character string found");
return value[0]; one_char = value[0];
return one_char;
} }
static constexpr auto name = _(PYBIND11_STRING_NAME); static constexpr auto name = _(PYBIND11_STRING_NAME);
template <typename _T> using cast_op_type = remove_reference_t<pybind11::detail::cast_op_type<_T>>; template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>;
}; };
// Base implementation for std::tuple and std::pair // Base implementation for std::tuple and std::pair

View File

@ -50,7 +50,9 @@ TEST_SUBMODULE(builtin_casters, m) {
// test_single_char_arguments // test_single_char_arguments
m.attr("wchar_size") = py::cast(sizeof(wchar_t)); m.attr("wchar_size") = py::cast(sizeof(wchar_t));
m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); }); m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); });
m.def("ord_char_lv", [](char &c) -> int { return static_cast<unsigned char>(c); });
m.def("ord_char16", [](char16_t c) -> uint16_t { return c; }); m.def("ord_char16", [](char16_t c) -> uint16_t { return c; });
m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; });
m.def("ord_char32", [](char32_t c) -> uint32_t { return c; }); m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
m.def("ord_wchar", [](wchar_t c) -> int { return c; }); m.def("ord_wchar", [](wchar_t c) -> int { return c; });

View File

@ -44,6 +44,7 @@ def test_single_char_arguments():
toolong_message = "Expected a character, but multi-character string found" toolong_message = "Expected a character, but multi-character string found"
assert m.ord_char(u'a') == 0x61 # simple ASCII assert m.ord_char(u'a') == 0x61 # simple ASCII
assert m.ord_char_lv(u'b') == 0x62
assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char
@ -54,9 +55,11 @@ def test_single_char_arguments():
assert m.ord_char16(u'a') == 0x61 assert m.ord_char16(u'a') == 0x61
assert m.ord_char16(u'é') == 0xE9 assert m.ord_char16(u'é') == 0xE9
assert m.ord_char16_lv(u'ê') == 0xEA
assert m.ord_char16(u'Ā') == 0x100 assert m.ord_char16(u'Ā') == 0x100
assert m.ord_char16(u'') == 0x203d assert m.ord_char16(u'') == 0x203d
assert m.ord_char16(u'') == 0x2665 assert m.ord_char16(u'') == 0x2665
assert m.ord_char16_lv(u'') == 0x2661
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair
assert str(excinfo.value) == toobig_message(0x10000) assert str(excinfo.value) == toobig_message(0x10000)