Commit 54031cb0 authored by Vitja Makarov's avatar Vitja Makarov

__metaclass__ support

Fill class attributes dict before class creation. Use bindings for class methods.
And use PyCFunction for staticmethods and __new__. Add simple testcase for __metaclass__.
And test for staticmethod as decorator
parent 581671b5
......@@ -1450,6 +1450,22 @@ class NameNode(AtomicExprNode):
return # There was an error earlier
if entry.is_builtin and Options.cache_builtins:
return # Lookup already cached
elif entry.is_real_dict:
assert entry.type.is_pyobject, "Python global or builtin not a Python object"
interned_cname = code.intern_identifier(self.entry.name)
if entry.is_builtin:
namespace = Naming.builtins_cname
else: # entry.is_pyglobal
namespace = entry.scope.namespace_cname
code.globalstate.use_utility_code(getitem_dict_utility_code)
code.putln(
'%s = __Pyx_PyDict_GetItem(%s, %s); %s' % (
self.result(),
namespace,
interned_cname,
code.error_goto_if_null(self.result(), self.pos)))
code.put_gotref(self.py_result())
elif entry.is_pyglobal or entry.is_builtin:
assert entry.type.is_pyobject, "Python global or builtin not a Python object"
interned_cname = code.intern_identifier(self.entry.name)
......@@ -1504,8 +1520,16 @@ class NameNode(AtomicExprNode):
rhs.free_temps(code)
# in Py2.6+, we need to invalidate the method cache
code.putln("PyType_Modified(%s);" %
entry.scope.parent_type.typeptr_cname)
else:
entry.scope.parent_type.typeptr_cname)
elif entry.is_real_dict:
code.put_error_if_neg(self.pos,
'PyDict_SetItem(%s, %s, %s)' % (
namespace,
interned_cname,
rhs.py_result()))
rhs.generate_disposal_code(code)
rhs.free_temps(code)
else:
code.put_error_if_neg(self.pos,
'PyObject_SetAttr(%s, %s, %s)' % (
namespace,
......@@ -1584,10 +1608,17 @@ class NameNode(AtomicExprNode):
if not self.entry.is_pyglobal:
error(self.pos, "Deletion of local or C global name not supported")
return
code.put_error_if_neg(self.pos,
'__Pyx_DelAttrString(%s, "%s")' % (
Naming.module_cname,
self.entry.name))
if self.entry.is_real_dict:
namespace = self.entry.scope.namespace_cname
code.put_error_if_neg(self.pos,
'PyDict_DelItemString(%s, "%s")' % (
namespace,
self.entry.name))
else:
code.put_error_if_neg(self.pos,
'__Pyx_DelAttrString(%s, "%s")' % (
Naming.module_cname,
self.entry.name))
def annotate(self, code):
if hasattr(self, 'is_called') and self.is_called:
......@@ -7127,16 +7158,38 @@ static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *na
""",
impl = """
static PyObject *__Pyx_CreateClass(
PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname)
PyObject *bases, PyObject *methods, PyObject *name, PyObject *modname)
{
PyObject *result = 0;
#if PY_MAJOR_VERSION < 3
PyObject *metaclass = 0, *base;
#endif
if (PyDict_SetItemString(dict, "__module__", modname) < 0)
if (PyDict_SetItemString(methods, "__module__", modname) < 0)
goto bad;
#if PY_MAJOR_VERSION < 3
result = PyClass_New(bases, dict, name);
metaclass = PyDict_GetItemString(methods, "__metaclass__");
if (metaclass != NULL)
Py_INCREF(metaclass);
else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) {
base = PyTuple_GET_ITEM(bases, 0);
metaclass = PyObject_GetAttrString(base, "__class__");
if (metaclass == NULL) {
PyErr_Clear();
metaclass = (PyObject *)base->ob_type;
Py_INCREF(metaclass);
}
}
else {
metaclass = (PyObject *) &PyClass_Type;
Py_INCREF(metaclass);
}
result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL);
#else
result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, dict, NULL);
/* it seems that python3+ handle __metaclass__ itself */
result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, methods, NULL);
#endif
bad:
return result;
......
......@@ -1959,6 +1959,9 @@ class DefNode(FuncDefNode):
# staticmethod() was overridden - not much we can do here ...
self.is_staticmethod = False
if self.name == '__new__':
self.is_staticmethod = 1
self.analyse_argument_types(env)
if self.name == '<lambda>':
self.declare_lambda_function(env)
......@@ -2203,9 +2206,11 @@ class DefNode(FuncDefNode):
def synthesize_assignment_node(self, env):
import ExprNodes
if env.is_py_class_scope:
rhs = ExprNodes.UnboundMethodNode(self.pos,
function = ExprNodes.PyCFunctionNode(self.pos,
pymethdef_cname = self.entry.pymethdef_cname))
rhs = ExprNodes.PyCFunctionNode(self.pos,
pymethdef_cname = self.entry.pymethdef_cname)
if not self.is_staticmethod and not self.is_classmethod:
rhs.binding = True
elif env.is_closure_scope:
rhs = ExprNodes.InnerFunctionNode(
self.pos, pymethdef_cname = self.entry.pymethdef_cname)
......@@ -3016,9 +3021,10 @@ class PyClassDefNode(ClassDefNode):
code.pyclass_stack.append(self)
cenv = self.scope
self.dict.generate_evaluation_code(code)
cenv.namespace_cname = cenv.class_obj_cname = self.dict.result()
self.body.generate_execution_code(code)
self.classobj.generate_evaluation_code(code)
cenv.namespace_cname = cenv.class_obj_cname = self.classobj.result()
self.body.generate_execution_code(code)
self.target.generate_assignment_code(self.classobj, code)
self.dict.generate_disposal_code(code)
self.dict.free_temps(code)
......
......@@ -70,6 +70,7 @@ class Entry(object):
# or class attribute during
# class construction
# is_member boolean Is an assigned class member
# is_real_dict boolean Is a real dict, PyClass attributes dict
# is_variable boolean Is a variable
# is_cfunction boolean Is a C function
# is_cmethod boolean Is a C method of an extension type
......@@ -131,6 +132,7 @@ class Entry(object):
is_cglobal = 0
is_pyglobal = 0
is_member = 0
is_real_dict = 0
is_variable = 0
is_cfunction = 0
is_cmethod = 0
......@@ -1405,6 +1407,7 @@ class PyClassScope(ClassScope):
entry = Scope.declare_var(self, name, type, pos,
cname, visibility, is_cdef)
entry.is_pyglobal = 1
entry.is_real_dict = 1
return entry
def add_default_value(self, type):
......@@ -1768,8 +1771,13 @@ static PyObject* __Pyx_Method_ClassMethod(PyObject *method) {
else if (PyCFunction_Check(method)) {
return PyClassMethod_New(method);
}
#ifdef __pyx_binding_PyCFunctionType_USED
else if (PyObject_TypeCheck(method, __pyx_binding_PyCFunctionType)) { /* binded CFunction */
return PyClassMethod_New(method);
}
#endif
PyErr_Format(PyExc_TypeError,
"Class-level classmethod() can only be called on"
"Class-level classmethod() can only be called on "
"a method_descriptor or instance method.");
return NULL;
}
......
......@@ -11,6 +11,10 @@ class2
class3
>>> class3.plus(1)
8
>>> class4.view()
class4
>>> class5.view()
class5
"""
def f_plus(cls, a):
......@@ -36,3 +40,11 @@ cdef class class3:
def view(cls):
print cls.__name__
view = classmethod(view)
class class4:
@classmethod
def view(cls):
print cls.__name__
class class5(class4):
pass
"""
>>> obj = Foo()
>>> obj.metaclass_was_here
True
"""
class Base(type):
def __new__(cls, name, bases, attrs):
attrs['metaclass_was_here'] = True
return type.__new__(cls, name, bases, attrs)
class Foo(object):
__metaclass__ = Base
......@@ -5,6 +5,8 @@ __doc__ = u"""
2
>>> class3.plus1(1)
2
>>> class4.plus1(1)
2
"""
def f_plus(a):
......@@ -18,3 +20,8 @@ class class2(object):
cdef class class3:
plus1 = f_plus
class class4:
@staticmethod
def plus1(a):
return a + 1
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment