From 1eaacd19f6de9a053570c21de6d173efc2304bc2 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 8 Feb 2017 02:45:51 -0500 Subject: [PATCH] Fix debugging output for nameless py::arg_v annotations (#648) * Fix debugging output for nameless py::arg annotations This fixes a couple bugs with nameless py::arg() (introduced in #634) annotations: - the argument name was being used in debug mode without checking that it exists (which would result in the std::string construction throwing an exception for being invoked with a nullptr) - the error output says "keyword arguments", but py::arg_v() can now also be used for positional argument defaults. - the debugging output "in function named 'blah'" was overly verbose: changed it to just "in function 'blah'". * Fix missing space in debug test string * Moved tests from issues to methods_and_attributes --- include/pybind11/attr.h | 10 ++++++---- tests/test_issues.cpp | 3 +-- tests/test_methods_and_attributes.cpp | 17 +++++++++++++++++ tests/test_methods_and_attributes.py | 24 ++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 129db0548..da3ac624f 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -303,19 +303,21 @@ template <> struct process_attribute : process_attribute_default { if (!a.value) { #if !defined(NDEBUG) - auto descr = "'" + std::string(a.name) + ": " + a.type + "'"; + std::string descr("'"); + if (a.name) descr += std::string(a.name) + ": "; + descr += a.type + "'"; if (r->is_method) { if (r->name) descr += " in method '" + (std::string) str(r->scope) + "." + (std::string) r->name + "'"; else descr += " in method of '" + (std::string) str(r->scope) + "'"; } else if (r->name) { - descr += " in function named '" + (std::string) r->name + "'"; + descr += " in function '" + (std::string) r->name + "'"; } - pybind11_fail("arg(): could not convert default keyword argument " + pybind11_fail("arg(): could not convert default argument " + descr + " into a Python object (type not registered yet?)"); #else - pybind11_fail("arg(): could not convert default keyword argument " + pybind11_fail("arg(): could not convert default argument " "into a Python object (type not registered yet?). " "Compile in debug mode for more information."); #endif diff --git a/tests/test_issues.cpp b/tests/test_issues.cpp index 4c59a1b12..7da370045 100644 --- a/tests/test_issues.cpp +++ b/tests/test_issues.cpp @@ -74,7 +74,6 @@ namespace std { template <> struct hash { size_t operator()(const TplConstrClass &t) const { return std::hash()(t.str); } }; } - void init_issues(py::module &m) { py::module m2 = m.def_submodule("issues"); @@ -397,5 +396,5 @@ void init_issues(py::module &m) { #endif } -// MSVC workaround: trying to use a lambda here crashes MSCV +// MSVC workaround: trying to use a lambda here crashes MSVC test_initializer issues(&init_issues); diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp index e33e4ac39..5bccf49cf 100644 --- a/tests/test_methods_and_attributes.cpp +++ b/tests/test_methods_and_attributes.cpp @@ -150,6 +150,9 @@ public: }; }} +/// Issue/PR #648: bad arg default debugging output +class NotRegistered {}; + test_initializer methods_and_attributes([](py::module &m) { py::class_(m, "ExampleMandA") .def(py::init<>()) @@ -270,4 +273,18 @@ test_initializer methods_and_attributes([](py::module &m) { m.def("floats_preferred", [](double f) { return 0.5 * f; }, py::arg("f")); m.def("floats_only", [](double f) { return 0.5 * f; }, py::arg("f").noconvert()); + /// Issue/PR #648: bad arg default debugging output +#if !defined(NDEBUG) + m.attr("debug_enabled") = true; +#else + m.attr("debug_enabled") = false; +#endif + m.def("bad_arg_def_named", []{ + auto m = py::module::import("pybind11_tests.issues"); + m.def("should_fail", [](int, NotRegistered) {}, py::arg(), py::arg("a") = NotRegistered()); + }); + m.def("bad_arg_def_unnamed", []{ + auto m = py::module::import("pybind11_tests.issues"); + m.def("should_fail", [](int, NotRegistered) {}, py::arg(), py::arg() = NotRegistered()); + }); }); diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 3a5d2e198..1ea669a43 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -255,3 +255,27 @@ def test_noconvert_args(msg): Invoked with: 4 """ + + +def test_bad_arg_default(msg): + from pybind11_tests import debug_enabled, bad_arg_def_named, bad_arg_def_unnamed + + with pytest.raises(RuntimeError) as excinfo: + bad_arg_def_named() + assert msg(excinfo.value) == ( + "arg(): could not convert default argument 'a: NotRegistered' in function 'should_fail' " + "into a Python object (type not registered yet?)" + if debug_enabled else + "arg(): could not convert default argument into a Python object (type not registered " + "yet?). Compile in debug mode for more information." + ) + + with pytest.raises(RuntimeError) as excinfo: + bad_arg_def_unnamed() + assert msg(excinfo.value) == ( + "arg(): could not convert default argument 'NotRegistered' in function 'should_fail' " + "into a Python object (type not registered yet?)" + if debug_enabled else + "arg(): could not convert default argument into a Python object (type not registered " + "yet?). Compile in debug mode for more information." + )