From a750a1ceff4b8ff9a6b0f70870b88de32cf64231 Mon Sep 17 00:00:00 2001 From: Vitja Makarov Date: Fri, 5 Nov 2010 13:56:18 +0300 Subject: [PATCH] Py3 metaclasses initial support. --- Cython/Compiler/ExprNodes.py | 142 ++++++++++++++++++++++++++++------- Cython/Compiler/Nodes.py | 6 +- Cython/Compiler/Parsing.py | 71 ++++++++++++------ tests/run/metaclass.pyx | 23 ++++++ 4 files changed, 186 insertions(+), 56 deletions(-) diff --git a/Cython/Compiler/ExprNodes.py b/Cython/Compiler/ExprNodes.py index 1a4ae861..33458e6d 100755 --- a/Cython/Compiler/ExprNodes.py +++ b/Cython/Compiler/ExprNodes.py @@ -4480,14 +4480,23 @@ class ClassNode(ExprNode, ModuleNameMixin): # dict ExprNode Class dict (not owned by this node) # doc ExprNode or None Doc string # module_name EncodedString Name of defining module + # keyword_args ExprNode or None Py3 Dict of keyword arguments, passed to __new__ + # starstar_arg ExprNode or None Py3 Dict of extra keyword args, same here - subexprs = ['bases', 'doc'] + subexprs = ['bases', 'keyword_args', 'starstar_arg', 'doc'] def analyse_types(self, env): self.bases.analyse_types(env) if self.doc: self.doc.analyse_types(env) self.doc = self.doc.coerce_to_pyobject(env) + if self.keyword_args: + self.keyword_args.analyse_types(env) + if self.starstar_arg: + self.starstar_arg.analyse_types(env) + if self.starstar_arg: + self.starstar_arg = \ + self.starstar_arg.coerce_to_pyobject(env) self.type = py_object_type self.is_temp = 1 env.use_utility_code(create_class_utility_code); @@ -4501,6 +4510,19 @@ class ClassNode(ExprNode, ModuleNameMixin): def generate_result_code(self, code): cname = code.intern_identifier(self.name) + if self.keyword_args and self.starstar_arg: + code.put_error_if_neg(self.pos, + "PyDict_Update(%s, %s)" % ( + self.keyword_args.py_result(), + self.starstar_arg.py_result())) + keyword_code = self.keyword_args.py_result() + elif self.keyword_args: + keyword_code = self.keyword_args.py_result() + elif self.starstar_arg: + keyword_code = self.starstar_arg.py_result() + else: + keyword_code = 'NULL' + if self.doc: code.put_error_if_neg(self.pos, 'PyDict_SetItemString(%s, "__doc__", %s)' % ( @@ -4508,12 +4530,13 @@ class ClassNode(ExprNode, ModuleNameMixin): self.doc.py_result())) py_mod_name = self.get_py_mod_name(code) code.putln( - '%s = __Pyx_CreateClass(%s, %s, %s, %s); %s' % ( + '%s = __Pyx_CreateClass(%s, %s, %s, %s, %s); %s' % ( self.result(), self.bases.py_result(), self.dict.py_result(), cname, py_mod_name, + keyword_code, code.error_goto_if_null(self.result(), self.pos))) code.put_gotref(self.py_result()) @@ -7159,45 +7182,106 @@ static CYTHON_INLINE int __Pyx_TypeTest(PyObject *obj, PyTypeObject *type) { create_class_utility_code = UtilityCode( proto = """ -static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname); /*proto*/ +static PyObject *__Pyx_CreateClass(PyObject *bases, PyObject *dict, PyObject *name, + PyObject *modname, PyObject *kwargs); /*proto*/ """, impl = """ +static int __Pyx_PrepareClass(PyObject *metaclass, PyObject *bases, PyObject *name, PyObject *mkw, PyObject *dict) +{ + PyObject *prep; + PyObject *pargs; + PyObject *ns; + + prep = PyObject_GetAttrString(metaclass, "__prepare__"); + if (prep == NULL) { + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return -1; + PyErr_Clear(); + return 0; + } + pargs = PyTuple_Pack(2, name, bases); + if (pargs == NULL) { + Py_DECREF(prep); + return -1; + } + ns = PyEval_CallObjectWithKeywords(prep, pargs, mkw); + Py_DECREF(pargs); + Py_DECREF(prep); + if (ns == NULL) + return -1; + /* XXX: This is hack, merge namespace back to dict, + __prepare__ should be ran before dict initialization */ + if (PyDict_Merge(dict, ns, 0)) { + Py_DECREF(ns); + return -1; + } + Py_DECREF(ns); + return 0; +} + static PyObject *__Pyx_CreateClass( - PyObject *bases, PyObject *methods, PyObject *name, PyObject *modname) + PyObject *bases, PyObject *dict, PyObject *name, PyObject *modname, PyObject *kwargs) { - PyObject *result = 0; -#if PY_MAJOR_VERSION < 3 - PyObject *metaclass = 0, *base; -#endif + PyObject *result = NULL; + PyObject *metaclass = NULL; + PyObject *mkw = NULL; - if (PyDict_SetItemString(methods, "__module__", modname) < 0) - goto bad; - #if PY_MAJOR_VERSION < 3 - metaclass = PyDict_GetItemString(methods, "__metaclass__"); + if (PyDict_SetItemString(dict, "__module__", modname) < 0) + return NULL; - 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; + /* Python3 metaclasses */ + if (kwargs) { + mkw = PyDict_Copy(kwargs); /* Don't modify kwargs passed in! */ + if (mkw == NULL) + return NULL; + metaclass = PyDict_GetItemString(mkw, "metaclass"); + if (metaclass) { Py_INCREF(metaclass); + if (PyDict_DelItemString(mkw, "metaclass") < 0) + goto bad; + if (__Pyx_PrepareClass(metaclass, bases, name, mkw, dict)) + goto bad; } } - else { - metaclass = (PyObject *) &PyClass_Type; + /* Python2 __metaclass__ */ + if (metaclass == NULL) { + metaclass = PyDict_GetItemString(dict, "__metaclass__"); + if (metaclass) + Py_INCREF(metaclass); + } + /* Default metaclass */ + if (metaclass == NULL) { +#if PY_MAJOR_VERSION < 3 + if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { + PyObject *base = PyTuple_GET_ITEM(bases, 0); + metaclass = PyObject_GetAttrString(base, "__class__"); + if (metaclass == NULL) { + PyErr_Clear(); + metaclass = (PyObject *)base->ob_type; + } + } else + metaclass = (PyObject *) &PyClass_Type; +#else + if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { + PyObject *base = PyTuple_GET_ITEM(bases, 0); + metaclass = (PyObject *)base->ob_type; + } else + metaclass = (PyObject *) &PyType_Type; +#endif Py_INCREF(metaclass); } - - result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, NULL); - Py_DECREF(metaclass); - #else - /* it seems that python3+ handle __metaclass__ itself */ - result = PyObject_CallFunctionObjArgs((PyObject *)&PyType_Type, name, bases, methods, NULL); - #endif + if (mkw && PyDict_Size(mkw) > 0) { + PyObject *margs; + margs = PyTuple_Pack(3, name, bases, dict, NULL); + result = PyEval_CallObjectWithKeywords(metaclass, margs, mkw); + Py_DECREF(margs); + } else { + result = PyObject_CallFunctionObjArgs(metaclass, name, bases, dict, NULL); + } bad: + Py_DECREF(metaclass); + if (mkw) + Py_XDECREF(mkw); return result; } """) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 0735e39d..1b6ab907 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -2940,7 +2940,8 @@ class PyClassDefNode(ClassDefNode): child_attrs = ["body", "dict", "classobj", "target"] decorators = None - def __init__(self, pos, name, bases, doc, body, decorators = None): + def __init__(self, pos, name, bases, doc, body, decorators = None, + keyword_args = None, starstar_arg = None): StatNode.__init__(self, pos) self.name = name self.doc = doc @@ -2955,7 +2956,8 @@ class PyClassDefNode(ClassDefNode): else: doc_node = None self.classobj = ExprNodes.ClassNode(pos, name = name, - bases = bases, dict = self.dict, doc = doc_node) + bases = bases, dict = self.dict, doc = doc_node, + keyword_args = keyword_args, starstar_arg = starstar_arg) self.target = ExprNodes.NameNode(pos, name = name) def as_cclass(self): diff --git a/Cython/Compiler/Parsing.py b/Cython/Compiler/Parsing.py index c79465d5..37582939 100644 --- a/Cython/Compiler/Parsing.py +++ b/Cython/Compiler/Parsing.py @@ -381,7 +381,7 @@ def p_trailer(s, node1): # arglist: argument (',' argument)* [','] # argument: [test '='] test # Really [keyword '='] test -def p_call(s, function): +def p_call_parse(s): # s.sy == '(' pos = s.position() s.next() @@ -428,29 +428,43 @@ def p_call(s, function): if s.sy == ',': s.next() s.expect(')') + return positional_args, keyword_args, star_arg, starstar_arg + +def p_call_prepare_full(pos, positional_args, keyword_args, star_arg): + arg_tuple = None + keyword_dict = None + if positional_args or not star_arg: + arg_tuple = ExprNodes.TupleNode(pos, + args = positional_args) + if star_arg: + star_arg_tuple = ExprNodes.AsTupleNode(pos, arg = star_arg) + if arg_tuple: + arg_tuple = ExprNodes.binop_node(pos, + operator = '+', operand1 = arg_tuple, + operand2 = star_arg_tuple) + else: + arg_tuple = star_arg_tuple + if keyword_args: + keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value) + for key, value in keyword_args] + keyword_dict = ExprNodes.DictNode(pos, + key_value_pairs = keyword_args) + return arg_tuple, keyword_dict + +def p_call(s, function): + # s.sy == '(' + pos = s.position() + + positional_args, keyword_args, star_arg, starstar_arg = \ + p_call_parse(s) + if not (keyword_args or star_arg or starstar_arg): return ExprNodes.SimpleCallNode(pos, function = function, args = positional_args) else: - arg_tuple = None - keyword_dict = None - if positional_args or not star_arg: - arg_tuple = ExprNodes.TupleNode(pos, - args = positional_args) - if star_arg: - star_arg_tuple = ExprNodes.AsTupleNode(pos, arg = star_arg) - if arg_tuple: - arg_tuple = ExprNodes.binop_node(pos, - operator = '+', operand1 = arg_tuple, - operand2 = star_arg_tuple) - else: - arg_tuple = star_arg_tuple - if keyword_args: - keyword_args = [ExprNodes.DictItemNode(pos=key.pos, key=key, value=value) - for key, value in keyword_args] - keyword_dict = ExprNodes.DictNode(pos, - key_value_pairs = keyword_args) + arg_tuple, keyword_dict = p_call_prepare_full(pos, + positional_args, keyword_args, star_arg) return ExprNodes.GeneralCallNode(pos, function = function, positional_args = arg_tuple, @@ -2607,16 +2621,23 @@ def p_class_statement(s, decorators): s.next() class_name = EncodedString( p_ident(s) ) class_name.encoding = s.source_encoding + arg_tuple = None + keyword_dict = None + starstar_arg = None if s.sy == '(': - s.next() - base_list = p_simple_expr_list(s) - s.expect(')') - else: - base_list = [] + positional_args, keyword_args, star_arg, starstar_arg = \ + p_call_parse(s) + arg_tuple, keyword_dict = p_call_prepare_full(pos, + positional_args, keyword_args, star_arg) + if arg_tuple is None: + # XXX: empty arg_tuple + arg_tuple = ExprNodes.TupleNode(pos, args = []) doc, body = p_suite(s, Ctx(level = 'class'), with_doc = 1) return Nodes.PyClassDefNode(pos, name = class_name, - bases = ExprNodes.TupleNode(pos, args = base_list), + bases = arg_tuple, + keyword_args = keyword_dict, + starstar_arg = starstar_arg, doc = doc, body = body, decorators = decorators) def p_c_class_definition(s, pos, ctx): diff --git a/tests/run/metaclass.pyx b/tests/run/metaclass.pyx index 56870382..ebf2c065 100644 --- a/tests/run/metaclass.pyx +++ b/tests/run/metaclass.pyx @@ -11,3 +11,26 @@ class Foo(object): True """ __metaclass__ = Base + +class Py3Base(type): + def __new__(cls, name, bases, attrs, foo=None): + attrs['foo'] = foo + return type.__new__(cls, name, bases, attrs) + + def __init__(self, cls, attrs, obj, foo=None): + pass + @staticmethod + def __prepare__(name, bases, **kwargs): + return {'bar': 666, 'dirty': True} + +class Py3Foo(object, metaclass=Py3Base, foo=123): + """ + >>> obj = Py3Foo() + >>> obj.foo + 123 + >>> obj.bar + 666 + >>> obj.dirty + False + """ + dirty = False -- 2.26.2