Moving tp_class access, and consistent fully-qualified naming for PyPy, to detail::get_tp_name (#2520)

* Moving tp_class access, and consistent fully-qualified naming for PyPy, to detail::get_tp_name

* Change get_tp_name to get_fully_qualified_tp_name
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index 0d3f17d..835406e 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -39,6 +39,9 @@
 PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
 PYBIND11_NAMESPACE_BEGIN(detail)
 
+// Forward-declaration; see detail/class.h
+std::string get_fully_qualified_tp_name(PyTypeObject*);
+
 /// A life support system for temporary objects created by `type_caster::load()`.
 /// Adding a patient will keep it alive up until the enclosing function returns.
 class loader_life_support {
@@ -342,8 +345,8 @@
             "(compile in debug mode for type details)");
 #else
     pybind11_fail("pybind11::detail::instance::get_value_and_holder: `" +
-            std::string(find_type->type->tp_name) + "' is not a pybind11 base of the given `" +
-            std::string(Py_TYPE(this)->tp_name) + "' instance");
+            get_fully_qualified_tp_name(find_type->type) + "' is not a pybind11 base of the given `" +
+            get_fully_qualified_tp_name(Py_TYPE(this)) + "' instance");
 #endif
 }
 
diff --git a/include/pybind11/detail/class.h b/include/pybind11/detail/class.h
index b4a11c0..0f432f2 100644
--- a/include/pybind11/detail/class.h
+++ b/include/pybind11/detail/class.h
@@ -24,6 +24,14 @@
 #  define PYBIND11_SET_OLDPY_QUALNAME(obj, nameobj) setattr((PyObject *) obj, "__qualname__", nameobj)
 #endif
 
+inline std::string get_fully_qualified_tp_name(PyTypeObject *type) {
+#if !defined(PYPY_VERSION)
+    return type->tp_name;
+#else
+    return handle((PyObject *) type).attr("__module__").cast<std::string>() + "." + type->tp_name;
+#endif
+}
+
 inline PyTypeObject *type_incref(PyTypeObject *type) {
     Py_INCREF(type);
     return type;
@@ -172,7 +180,7 @@
     for (const auto &vh : values_and_holders(instance)) {
         if (!vh.holder_constructed()) {
             PyErr_Format(PyExc_TypeError, "%.200s.__init__() must be called when overriding __init__",
-                         vh.type->type->tp_name);
+                         get_fully_qualified_tp_name(vh.type->type).c_str());
             Py_DECREF(self);
             return nullptr;
         }
@@ -304,12 +312,7 @@
 /// following default function will be used which simply throws an exception.
 extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject *) {
     PyTypeObject *type = Py_TYPE(self);
-    std::string msg;
-#if defined(PYPY_VERSION)
-    msg += handle((PyObject *) type).attr("__module__").cast<std::string>() + ".";
-#endif
-    msg += type->tp_name;
-    msg += ": No constructor defined!";
+    std::string msg = get_fully_qualified_tp_name(type) + ": No constructor defined!";
     PyErr_SetString(PyExc_TypeError, msg.c_str());
     return -1;
 }
@@ -448,7 +451,7 @@
 extern "C" inline int pybind11_set_dict(PyObject *self, PyObject *new_dict, void *) {
     if (!PyDict_Check(new_dict)) {
         PyErr_Format(PyExc_TypeError, "__dict__ must be set to a dictionary, not a '%.200s'",
-                     Py_TYPE(new_dict)->tp_name);
+                     get_fully_qualified_tp_name(Py_TYPE(new_dict)).c_str());
         return -1;
     }
     PyObject *&dict = *_PyObject_GetDictPtr(self);
@@ -476,9 +479,8 @@
 inline void enable_dynamic_attributes(PyHeapTypeObject *heap_type) {
     auto type = &heap_type->ht_type;
 #if defined(PYPY_VERSION) && (PYPY_VERSION_NUM < 0x06000000)
-    pybind11_fail(std::string(type->tp_name) + ": dynamic attributes are "
-                                               "currently not supported in "
-                                               "conjunction with PyPy!");
+    pybind11_fail(get_fully_qualified_tp_name(type) + ": dynamic attributes are currently not "
+                                                      "supported in conjunction with PyPy!");
 #endif
     type->tp_flags |= Py_TPFLAGS_HAVE_GC;
     type->tp_dictoffset = type->tp_basicsize; // place dict at the end
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 0cde4fa..857f627 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -242,7 +242,7 @@
 
 #if !defined(NDEBUG) && !defined(PYBIND11_DISABLE_NEW_STYLE_INIT_WARNING)
         if (rec->is_constructor && !rec->is_new_style_constructor) {
-            const auto class_name = std::string(((PyTypeObject *) rec->scope.ptr())->tp_name);
+            const auto class_name = detail::get_fully_qualified_tp_name((PyTypeObject *) rec->scope.ptr());
             const auto func_name = std::string(rec->name);
             PyErr_WarnEx(
                 PyExc_FutureWarning,
@@ -1039,7 +1039,7 @@
         if (!type->ht_type.tp_as_buffer)
             pybind11_fail(
                 "To be able to register buffer protocol support for the type '" +
-                std::string(tinfo->type->tp_name) +
+                get_fully_qualified_tp_name(tinfo->type) +
                 "' the associated class<>(..) invocation must "
                 "include the pybind11::buffer_protocol() annotation!");
 
diff --git a/tests/test_class.py b/tests/test_class.py
index 64f4941..fb061d1 100644
--- a/tests/test_class.py
+++ b/tests/test_class.py
@@ -152,10 +152,8 @@
             pass
     with pytest.raises(TypeError) as exc_info:
         Python()
-    expected = ["m.class_.Pet.__init__() must be called when overriding __init__",
-                "Pet.__init__() must be called when overriding __init__"]  # PyPy?
-    # TODO: fix PyPy error message wrt. tp_name/__qualname__?
-    assert msg(exc_info.value) in expected
+    expected = "m.class_.Pet.__init__() must be called when overriding __init__"
+    assert msg(exc_info.value) == expected
 
     # Multiple bases
     class RabbitHamster(m.Rabbit, m.Hamster):
@@ -164,9 +162,8 @@
 
     with pytest.raises(TypeError) as exc_info:
         RabbitHamster()
-    expected = ["m.class_.Hamster.__init__() must be called when overriding __init__",
-                "Hamster.__init__() must be called when overriding __init__"]  # PyPy
-    assert msg(exc_info.value) in expected
+    expected = "m.class_.Hamster.__init__() must be called when overriding __init__"
+    assert msg(exc_info.value) == expected
 
 
 def test_automatic_upcasting():
diff --git a/tests/test_local_bindings.py b/tests/test_local_bindings.py
index 5460727..ebc4873 100644
--- a/tests/test_local_bindings.py
+++ b/tests/test_local_bindings.py
@@ -155,7 +155,7 @@
     assert m.local_cpp_types_addr() != cm.local_cpp_types_addr()
 
 
-@pytest.mark.xfail("env.PYPY")
+@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 2)")
 def test_stl_caster_vs_stl_bind(msg):
     """One module uses a generic vector caster from `<pybind11/stl.h>` while the other
     exports `std::vector<int>` via `py:bind_vector` and `py::module_local`"""