Reimplement static properties by extending PyProperty_Type

Instead of creating a new unique metaclass for each type, the builtin
`property` type is subclassed to support static properties. The new
setter/getters always pass types instead of instances in their `self`
argument. A metaclass is still required to support this behavior, but
it doesn't store any data anymore, so a new one doesn't need to be
created for each class. There is now only one common metaclass which
is shared by all pybind11 types.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 63f3483..4305cee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -56,6 +56,7 @@
   include/pybind11/attr.h
   include/pybind11/cast.h
   include/pybind11/chrono.h
+  include/pybind11/class_support.h
   include/pybind11/common.h
   include/pybind11/complex.h
   include/pybind11/descr.h
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index cfc6f8b..a2e7f02 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -18,6 +18,8 @@
 
 NAMESPACE_BEGIN(pybind11)
 NAMESPACE_BEGIN(detail)
+inline PyTypeObject *make_static_property_type();
+inline PyTypeObject *make_default_metaclass();
 
 /// Additional type information which does not fit into the PyTypeObject
 struct type_info {
@@ -73,6 +75,8 @@
                 }
             }
         );
+        internals_ptr->static_property_type = make_static_property_type();
+        internals_ptr->default_metaclass = make_default_metaclass();
     }
     return *internals_ptr;
 }
diff --git a/include/pybind11/class_support.h b/include/pybind11/class_support.h
new file mode 100644
index 0000000..ed2eade
--- /dev/null
+++ b/include/pybind11/class_support.h
@@ -0,0 +1,147 @@
+/*
+    pybind11/class_support.h: Python C API implementation details for py::class_
+
+    Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
+
+    All rights reserved. Use of this source code is governed by a
+    BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include "attr.h"
+
+NAMESPACE_BEGIN(pybind11)
+NAMESPACE_BEGIN(detail)
+
+#if !defined(PYPY_VERSION)
+
+/// `pybind11_static_property.__get__()`: Always pass the class instead of the instance.
+extern "C" inline PyObject *pybind11_static_get(PyObject *self, PyObject * /*ob*/, PyObject *cls) {
+    return PyProperty_Type.tp_descr_get(self, cls, cls);
+}
+
+/// `pybind11_static_property.__set__()`: Just like the above `__get__()`.
+extern "C" inline int pybind11_static_set(PyObject *self, PyObject *obj, PyObject *value) {
+    PyObject *cls = PyType_Check(obj) ? obj : (PyObject *) Py_TYPE(obj);
+    return PyProperty_Type.tp_descr_set(self, cls, value);
+}
+
+/** A `static_property` is the same as a `property` but the `__get__()` and `__set__()`
+    methods are modified to always use the object type instead of a concrete instance.
+    Return value: New reference. */
+inline PyTypeObject *make_static_property_type() {
+    constexpr auto *name = "pybind11_static_property";
+    auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
+
+    /* Danger zone: from now (and until PyType_Ready), make sure to
+       issue no Python C API calls which could potentially invoke the
+       garbage collector (the GC will call type_traverse(), which will in
+       turn find the newly constructed type in an invalid state) */
+    auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
+    if (!heap_type)
+        pybind11_fail("make_static_property_type(): error allocating type!");
+
+    heap_type->ht_name = name_obj.inc_ref().ptr();
+#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
+    heap_type->ht_qualname = name_obj.inc_ref().ptr();
+#endif
+
+    auto type = &heap_type->ht_type;
+    type->tp_name = name;
+    type->tp_base = &PyProperty_Type;
+    type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
+    type->tp_descr_get = pybind11_static_get;
+    type->tp_descr_set = pybind11_static_set;
+
+    if (PyType_Ready(type) < 0)
+        pybind11_fail("make_static_property_type(): failure in PyType_Ready()!");
+
+    return type;
+}
+
+#else // PYPY
+
+/** PyPy has some issues with the above C API, so we evaluate Python code instead.
+    This function will only be called once so performance isn't really a concern.
+    Return value: New reference. */
+inline PyTypeObject *make_static_property_type() {
+    auto d = dict();
+    PyObject *result = PyRun_String(R"(\
+        class pybind11_static_property(property):
+            def __get__(self, obj, cls):
+                return property.__get__(self, cls, cls)
+
+            def __set__(self, obj, value):
+                cls = obj if isinstance(obj, type) else type(obj)
+                property.__set__(self, cls, value)
+        )", Py_file_input, d.ptr(), d.ptr()
+    );
+    if (result == nullptr)
+        throw error_already_set();
+    Py_DECREF(result);
+    return (PyTypeObject *) d["pybind11_static_property"].cast<object>().release().ptr();
+}
+
+#endif // PYPY
+
+/** Types with static properties need to handle `Type.static_prop = x` in a specific way.
+    By default, Python replaces the `static_property` itself, but for wrapped C++ types
+    we need to call `static_property.__set__()` in order to propagate the new value to
+    the underlying C++ data structure. */
+extern "C" inline int pybind11_meta_setattro(PyObject* obj, PyObject* name, PyObject* value) {
+    // Use `_PyType_Lookup()` instead of `PyObject_GetAttr()` in order to get the raw
+    // descriptor (`property`) instead of calling `tp_descr_get` (`property.__get__()`).
+    PyObject *descr = _PyType_Lookup((PyTypeObject *) obj, name);
+
+    // Call `static_property.__set__()` instead of replacing the `static_property`.
+    if (descr && PyObject_IsInstance(descr, (PyObject *) get_internals().static_property_type)) {
+#if !defined(PYPY_VERSION)
+        return Py_TYPE(descr)->tp_descr_set(descr, obj, value);
+#else
+        if (PyObject *result = PyObject_CallMethod(descr, "__set__", "OO", obj, value)) {
+            Py_DECREF(result);
+            return 0;
+        } else {
+            return -1;
+        }
+#endif
+    } else {
+        return PyType_Type.tp_setattro(obj, name, value);
+    }
+}
+
+/** This metaclass is assigned by default to all pybind11 types and is required in order
+    for static properties to function correctly. Users may override this using `py::metaclass`.
+    Return value: New reference. */
+inline PyTypeObject* make_default_metaclass() {
+    constexpr auto *name = "pybind11_type";
+    auto name_obj = reinterpret_steal<object>(PYBIND11_FROM_STRING(name));
+
+    /* Danger zone: from now (and until PyType_Ready), make sure to
+       issue no Python C API calls which could potentially invoke the
+       garbage collector (the GC will call type_traverse(), which will in
+       turn find the newly constructed type in an invalid state) */
+    auto heap_type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
+    if (!heap_type)
+        pybind11_fail("make_default_metaclass(): error allocating metaclass!");
+
+    heap_type->ht_name = name_obj.inc_ref().ptr();
+#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
+    heap_type->ht_qualname = name_obj.inc_ref().ptr();
+#endif
+
+    auto type = &heap_type->ht_type;
+    type->tp_name = name;
+    type->tp_base = &PyType_Type;
+    type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HEAPTYPE;
+    type->tp_setattro = pybind11_meta_setattro;
+
+    if (PyType_Ready(type) < 0)
+        pybind11_fail("make_default_metaclass(): failure in PyType_Ready()!");
+
+    return type;
+}
+
+NAMESPACE_END(detail)
+NAMESPACE_END(pybind11)
diff --git a/include/pybind11/common.h b/include/pybind11/common.h
index c3f1e30..a367e75 100644
--- a/include/pybind11/common.h
+++ b/include/pybind11/common.h
@@ -352,7 +352,7 @@
     }
 };
 
-/// Internal data struture used to track registered instances and types
+/// Internal data structure used to track registered instances and types
 struct internals {
     std::unordered_map<std::type_index, void*> registered_types_cpp;   // std::type_index -> type_info
     std::unordered_map<const void *, void*> registered_types_py;       // PyTypeObject* -> type_info
@@ -361,6 +361,8 @@
     std::unordered_map<std::type_index, std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
     std::forward_list<void (*) (std::exception_ptr)> registered_exception_translators;
     std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
+    PyTypeObject *static_property_type;
+    PyTypeObject *default_metaclass;
 #if defined(WITH_THREAD)
     decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
     PyInterpreterState *istate = nullptr;
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 5a604cd..6cc6d5e 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -35,6 +35,7 @@
 
 #include "attr.h"
 #include "options.h"
+#include "class_support.h"
 
 NAMESPACE_BEGIN(pybind11)
 
@@ -818,15 +819,12 @@
         object scope_qualname;
         if (rec->scope && hasattr(rec->scope, "__qualname__"))
             scope_qualname = rec->scope.attr("__qualname__");
-        object ht_qualname, ht_qualname_meta;
+        object ht_qualname;
         if (scope_qualname)
             ht_qualname = reinterpret_steal<object>(PyUnicode_FromFormat(
                 "%U.%U", scope_qualname.ptr(), name.ptr()));
         else
             ht_qualname = name;
-        if (rec->metaclass)
-            ht_qualname_meta = reinterpret_steal<object>(
-                PyUnicode_FromFormat("%U__Meta", ht_qualname.ptr()));
 #endif
 
 #if !defined(PYPY_VERSION)
@@ -836,36 +834,6 @@
         std::string full_name = std::string(rec->name);
 #endif
 
-        /* Create a custom metaclass if requested (used for static properties) */
-        object metaclass;
-        if (rec->metaclass) {
-            std::string meta_name_ = full_name + "__Meta";
-            object meta_name = reinterpret_steal<object>(PYBIND11_FROM_STRING(meta_name_.c_str()));
-            metaclass = reinterpret_steal<object>(PyType_Type.tp_alloc(&PyType_Type, 0));
-            if (!metaclass || !name)
-                pybind11_fail("generic_type::generic_type(): unable to create metaclass!");
-
-            /* Danger zone: from now (and until PyType_Ready), make sure to
-               issue no Python C API calls which could potentially invoke the
-               garbage collector (the GC will call type_traverse(), which will in
-               turn find the newly constructed type in an invalid state) */
-
-            auto type = (PyHeapTypeObject*) metaclass.ptr();
-            type->ht_name = meta_name.release().ptr();
-
-#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3
-            /* Qualified names for Python >= 3.3 */
-            type->ht_qualname = ht_qualname_meta.release().ptr();
-#endif
-            type->ht_type.tp_name = strdup(meta_name_.c_str());
-            type->ht_type.tp_base = &PyType_Type;
-            type->ht_type.tp_flags |= (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HEAPTYPE) &
-                                      ~Py_TPFLAGS_HAVE_GC;
-
-            if (PyType_Ready(&type->ht_type) < 0)
-                pybind11_fail("generic_type::generic_type(): failure in PyType_Ready() for metaclass!");
-        }
-
         size_t num_bases = rec->bases.size();
         auto bases = tuple(rec->bases);
 
@@ -915,8 +883,9 @@
         type->ht_qualname = ht_qualname.release().ptr();
 #endif
 
-        /* Metaclass */
-        PYBIND11_OB_TYPE(type->ht_type) = (PyTypeObject *) metaclass.release().ptr();
+        /* Custom metaclass if requested (used for static properties) */
+        if (rec->metaclass)
+            PYBIND11_OB_TYPE(type->ht_type) = internals.default_metaclass;
 
         /* Supported protocols */
         type->ht_type.tp_as_number = &type->as_number;
@@ -1105,15 +1074,10 @@
     void def_property_static_impl(const char *name,
                                   handle fget, handle fset,
                                   detail::function_record *rec_fget) {
-        pybind11::str doc_obj = pybind11::str(
-            (rec_fget->doc && pybind11::options::show_user_defined_docstrings())
-                ? rec_fget->doc : "");
-        const auto property = reinterpret_steal<object>(
-            PyObject_CallFunctionObjArgs((PyObject *) &PyProperty_Type, fget.ptr() ? fget.ptr() : Py_None,
-                                         fset.ptr() ? fset.ptr() : Py_None, Py_None, doc_obj.ptr(), nullptr));
-        if (rec_fget->is_method && rec_fget->scope) {
-            attr(name) = property;
-        } else {
+        const auto is_static = !(rec_fget->is_method && rec_fget->scope);
+        const auto has_doc = rec_fget->doc && pybind11::options::show_user_defined_docstrings();
+
+        if (is_static) {
             auto mclass = handle((PyObject *) PYBIND11_OB_TYPE(*((PyTypeObject *) m_ptr)));
 
             if ((PyTypeObject *) mclass.ptr() == &PyType_Type)
@@ -1123,8 +1087,14 @@
                     "' requires the type to have a custom metaclass. Please "
                     "ensure that one is created by supplying the pybind11::metaclass() "
                     "annotation to the associated class_<>(..) invocation.");
-            mclass.attr(name) = property;
         }
+
+        auto property = handle((PyObject *) (is_static ? get_internals().static_property_type
+                                                       : &PyProperty_Type));
+        attr(name) = property(fget.ptr() ? fget : none(),
+                              fset.ptr() ? fset : none(),
+                              /*deleter*/none(),
+                              pybind11::str(has_doc ? rec_fget->doc : ""));
     }
 };
 
diff --git a/setup.py b/setup.py
index f3011b0..0cf4e47 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@
         'include/pybind11/attr.h',
         'include/pybind11/cast.h',
         'include/pybind11/chrono.h',
+        'include/pybind11/class_support.h',
         'include/pybind11/common.h',
         'include/pybind11/complex.h',
         'include/pybind11/descr.h',
diff --git a/tests/test_methods_and_attributes.cpp b/tests/test_methods_and_attributes.cpp
index 5bccf49..11c3640 100644
--- a/tests/test_methods_and_attributes.cpp
+++ b/tests/test_methods_and_attributes.cpp
@@ -214,7 +214,10 @@
                                       [](py::object) { return TestProperties::static_get(); })
         .def_property_static("def_property_static",
                              [](py::object) { return TestProperties::static_get(); },
-                             [](py::object, int v) { return TestProperties::static_set(v); });
+                             [](py::object, int v) { TestProperties::static_set(v); })
+        .def_property_static("static_cls",
+                             [](py::object cls) { return cls; },
+                             [](py::object cls, py::function f) { f(cls); });
 
     py::class_<SimpleValue>(m, "SimpleValue")
         .def_readwrite("value", &SimpleValue::value);
diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py
index 1ea669a..b5d5afd 100644
--- a/tests/test_methods_and_attributes.py
+++ b/tests/test_methods_and_attributes.py
@@ -84,19 +84,47 @@
     from pybind11_tests import TestProperties as Type
 
     assert Type.def_readonly_static == 1
-    with pytest.raises(AttributeError):
+    with pytest.raises(AttributeError) as excinfo:
         Type.def_readonly_static = 2
+    assert "can't set attribute" in str(excinfo)
 
     Type.def_readwrite_static = 2
     assert Type.def_readwrite_static == 2
 
     assert Type.def_property_readonly_static == 2
-    with pytest.raises(AttributeError):
+    with pytest.raises(AttributeError) as excinfo:
         Type.def_property_readonly_static = 3
+    assert "can't set attribute" in str(excinfo)
 
     Type.def_property_static = 3
     assert Type.def_property_static == 3
 
+    # Static property read and write via instance
+    instance = Type()
+
+    Type.def_readwrite_static = 0
+    assert Type.def_readwrite_static == 0
+    assert instance.def_readwrite_static == 0
+
+    instance.def_readwrite_static = 2
+    assert Type.def_readwrite_static == 2
+    assert instance.def_readwrite_static == 2
+
+
+def test_static_cls():
+    """Static property getter and setters expect the type object as the their only argument"""
+    from pybind11_tests import TestProperties as Type
+
+    instance = Type()
+    assert Type.static_cls is Type
+    assert instance.static_cls is Type
+
+    def check_self(self):
+        assert self is Type
+
+    Type.static_cls = check_self
+    instance.static_cls = check_self
+
 
 @pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
 def test_property_return_value_policies(access):
diff --git a/tests/test_python_types.py b/tests/test_python_types.py
index c5ade90..cf5412d 100644
--- a/tests/test_python_types.py
+++ b/tests/test_python_types.py
@@ -6,7 +6,7 @@
 
 def test_repr():
     # In Python 3.3+, repr() accesses __qualname__
-    assert "ExamplePythonTypes__Meta" in repr(type(ExamplePythonTypes))
+    assert "pybind11_type" in repr(type(ExamplePythonTypes))
     assert "ExamplePythonTypes" in repr(ExamplePythonTypes)