diff --git a/example/issues.cpp b/example/issues.cpp index 83147592..55fc3f3c 100644 --- a/example/issues.cpp +++ b/example/issues.cpp @@ -138,4 +138,23 @@ void init_issues(py::module &m) { } catch (std::runtime_error &) { /* All good */ } + + // Issue #283: __str__ called on uninitialized instance when constructor arguments invalid + class StrIssue { + public: + StrIssue(int i) : val{i} {} + StrIssue() : StrIssue(-1) {} + int value() const { return val; } + private: + int val; + }; + py::class_ si(m2, "StrIssue"); + si .def(py::init()) + .def(py::init<>()) + .def("__str__", [](const StrIssue &si) { + std::cout << "StrIssue.__str__ called" << std::endl; + return "StrIssue[" + std::to_string(si.value()) + "]"; + }) + ; + } diff --git a/example/issues.py b/example/issues.py index 257f08e1..716a1b2f 100644 --- a/example/issues.py +++ b/example/issues.py @@ -10,6 +10,7 @@ from example.issues import iterator_passthrough from example.issues import ElementList, ElementA, print_element from example.issues import expect_float, expect_int from example.issues import A, call_f +from example.issues import StrIssue import gc print_cchar("const char *") @@ -72,3 +73,8 @@ print("Python version") b = B() call_f(b) +print(StrIssue(3)) +try: + print(StrIssue("no", "such", "constructor")) +except TypeError as e: + print("Failed as expected: " + str(e)) diff --git a/example/issues.ref b/example/issues.ref index 58cc7985..0386d2c6 100644 --- a/example/issues.ref +++ b/example/issues.ref @@ -18,3 +18,9 @@ Python version PyA.PyA() PyA.f() In python f() +StrIssue.__str__ called +StrIssue[3] +Failed as expected: Incompatible constructor arguments. The following argument types are supported: + 1. example.issues.StrIssue(int) + 2. example.issues.StrIssue() + Invoked with: no, such, constructor diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 5718d031..02296671 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -445,17 +445,38 @@ protected: } if (result.ptr() == PYBIND11_TRY_NEXT_OVERLOAD) { - std::string msg = "Incompatible function arguments. The " - "following argument types are supported:\n"; + std::string msg = "Incompatible " + std::string(overloads->is_constructor ? "constructor" : "function") + + " arguments. The following argument types are supported:\n"; int ctr = 0; for (detail::function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) { msg += " "+ std::to_string(++ctr) + ". "; - msg += it2->signature; + + bool wrote_sig = false; + if (overloads->is_constructor) { + // For a constructor, rewrite `(Object, arg0, ...) -> NoneType` as `Object(arg0, ...)` + std::string sig = it2->signature; + size_t start = sig.find('(') + 1; + if (start < sig.size()) { + // End at the , for the next argument + size_t end = sig.find(", "), next = end + 2; + size_t ret = sig.rfind(" -> "); + // Or the ), if there is no comma: + if (end >= sig.size()) next = end = sig.find(')'); + if (start < end && next < sig.size()) { + msg.append(sig, start, end - start); + msg += '('; + msg.append(sig, next, ret - next); + wrote_sig = true; + } + } + } + if (!wrote_sig) msg += it2->signature; + msg += "\n"; } msg += " Invoked with: "; tuple args_(args, true); - for( std::size_t ti = 0; ti != args_.size(); ++ti) + for( std::size_t ti = overloads->is_constructor ? 1 : 0; ti < args_.size(); ++ti) { msg += static_cast(static_cast(args_[ti]).str()); if ((ti + 1) != args_.size() )