Commits

Amaury Forgeot d'Arc committed 444ebc9

New class definition protocol: __build_class__, __prepare__
For now it's not possible to use it, the grammar does
not allow the new syntax: class X(metaclass=meta)

  • Participants
  • Parent commits 773f101
  • Branches py3k

Comments (0)

Files changed (6)

File pypy/interpreter/astcompiler/assemble.py

     ops.PRINT_EXPR : -1,
 
     ops.WITH_CLEANUP : -1,
+    ops.LOAD_BUILD_CLASS : 1,
+    ops.STORE_LOCALS : -1,
     ops.POP_BLOCK : 0,
     ops.END_FINALLY : -3,
     ops.SETUP_WITH : 1,

File pypy/interpreter/astcompiler/astbuilder.py

         name = name_node.value
         self.check_forbidden_name(name, name_node)
         if len(classdef_node.children) == 4:
+            # class NAME ':' suite
             body = self.handle_suite(classdef_node.children[3])
             return ast.ClassDef(name, None, None, None, None, body, decorators,
                                 classdef_node.lineno, classdef_node.column)
         if classdef_node.children[3].type == tokens.RPAR:
+            # class NAME '(' ')' ':' suite
             body = self.handle_suite(classdef_node.children[5])
             return ast.ClassDef(name, None, None, None, None, body, decorators,
                                 classdef_node.lineno, classdef_node.column)
-        bases = self.handle_class_bases(classdef_node.children[3])
+
+        # class NAME '(' arglist ')' ':' suite
+        # build up a fake Call node so we can extract its pieces
+        call_name = ast.Name(name, ast.Load, classdef_node.lineno,
+                             classdef_node.column)
+        call = self.handle_call(classdef_node.children[3], call_name)
         body = self.handle_suite(classdef_node.children[6])
-        return ast.ClassDef(name, bases, None, None, None, body, decorators,
-                            classdef_node.lineno, classdef_node.column)
+        return ast.ClassDef(
+            name, call.args, call.keywords, call.starargs, call.kwargs,
+            body, decorators, classdef_node.lineno, classdef_node.column)
 
     def handle_class_bases(self, bases_node):
         if len(bases_node.children) == 1:

File pypy/interpreter/astcompiler/codegen.py

     def visit_ClassDef(self, cls):
         self.update_position(cls.lineno, True)
         self.visit_sequence(cls.decorator_list)
+        # 1. compile the class body into a code object
+        code = self.sub_scope(ClassCodeGenerator, cls.name, cls, cls.lineno)
+        # 2. load the 'build_class' function
+        self.emit_op(ops.LOAD_BUILD_CLASS)
+        # 3. load a function (or closure) made from the code object
+        self._make_function(code, 0)
+        # 4. load class name
         self.load_const(self.space.wrap(cls.name))
-        self.visit_sequence(cls.bases)
-        bases_count = len(cls.bases) if cls.bases is not None else 0
-        self.emit_op_arg(ops.BUILD_TUPLE, bases_count)
-        code = self.sub_scope(ClassCodeGenerator, cls.name, cls, cls.lineno)
-        self._make_function(code, 0)
-        self.emit_op_arg(ops.CALL_FUNCTION, 0)
-        self.emit_op(ops.BUILD_CLASS)
+        # 5. generate the rest of the code for the call
+        self._make_call(2,
+                        cls.bases, cls.keywords,
+                        cls.starargs, cls.kwargs)
+        # 6. apply decorators
         if cls.decorator_list:
             for i in range(len(cls.decorator_list)):
                 self.emit_op_arg(ops.CALL_FUNCTION, 1)
+        # 7. store into <name>
         self.name_op(cls.name, ast.Store)
 
     def _op_for_augassign(self, op):
         self.load_const(self.space.wrap(keyword.arg))
         keyword.value.walkabout(self)
 
-    def visit_Call(self, call):
-        self.update_position(call.lineno)
-        if self._optimize_method_call(call):
-            return
-        call.func.walkabout(self)
-        arg = len(call.args) if call.args is not None else 0
+    def _make_call(self, n, # args already pushed
+                   args, keywords, starargs, kwargs):
+        if args is not None:
+            arg = len(args) + n
+        else:
+            arg = n
         call_type = 0
-        self.visit_sequence(call.args)
-        if call.keywords:
-            self.visit_sequence(call.keywords)
-            arg |= len(call.keywords) << 8
-        if call.starargs:
-            call.starargs.walkabout(self)
+        self.visit_sequence(args)
+        if keywords:
+            self.visit_sequence(keywords)
+            arg |= len(keywords) << 8
+        if starargs:
+            starargs.walkabout(self)
             call_type |= 1
-        if call.kwargs:
-            call.kwargs.walkabout(self)
+        if kwargs:
+            kwargs.walkabout(self)
             call_type |= 2
         op = 0
         if call_type == 0:
         elif call_type == 3:
             op = ops.CALL_FUNCTION_VAR_KW
         self.emit_op_arg(op, arg)
+
+    def visit_Call(self, call):
+        self.update_position(call.lineno)
+        if self._optimize_method_call(call):
+            return
+        call.func.walkabout(self)
+        self._make_call(0,
+                        call.args, call.keywords,
+                        call.starargs, call.kwargs)
     
     def _call_has_no_star_args(self, call):
         return not call.starargs and not call.kwargs
     def _compile(self, cls):
         assert isinstance(cls, ast.ClassDef)
         self.lineno = self.first_lineno
+        self.argcount = 1
+        # load the first argument (__locals__) ...
+        self.emit_op_arg(ops.LOAD_FAST, 0)
+        # ...and store it into f_locals.
+        self.emit_op(ops.STORE_LOCALS)
+        # load (global) __name__ ...
         self.name_op("__name__", ast.Load)
+        # ... and store it as __module__
         self.name_op("__module__", ast.Store)
+        # compile the body proper
         self._handle_body(cls.body)
-        self.emit_op(ops.LOAD_LOCALS)
+        # return the (empty) __class__ cell
+        scope = self.scope.lookup("@__class__")
+        if scope == symtable.SCOPE_UNKNOWN:
+            # This happens when nobody references the cell
+            self.load_const(self.space.w_None)
+        else:
+            # Return the cell where to store __class__
+            self.emit_op_arg(ops.LOAD_CLOSURE, self.cell_vars["@__class__"])
         self.emit_op(ops.RETURN_VALUE)

File pypy/interpreter/pyopcode.py

     def LOAD_LOCALS(self, oparg, next_instr):
         self.pushvalue(self.w_locals)
 
+    def STORE_LOCALS(self, oparg, next_instr):
+        self.w_locals = self.popvalue()
+
     def EXEC_STMT(self, oparg, next_instr):
         w_locals = self.popvalue()
         w_globals = self.popvalue()
         unroller = self.space.interpclass_w(w_unroller)
         return unroller
 
-    def BUILD_CLASS(self, oparg, next_instr):
-        w_methodsdict = self.popvalue()
-        w_bases = self.popvalue()
-        w_name = self.popvalue()
-        w_metaclass = find_metaclass(self.space, w_bases,
-                                     w_methodsdict, self.w_globals,
-                                     self.space.wrap(self.get_builtin()))
-        w_newclass = self.space.call_function(w_metaclass, w_name,
-                                              w_bases, w_methodsdict)
-        self.pushvalue(w_newclass)
+    def LOAD_BUILD_CLASS(self, oparg, next_instr):
+        w_build_class = self.get_builtin().getdictvalue(
+            self.space, '__build_class__')
+        if w_build_class is None:
+            raise OperationError(self.space.w_ImportError,
+                                 self.space.wrap("__build_class__ not found"))
+        self.pushvalue(w_build_class)
 
     def STORE_NAME(self, varindex, next_instr):
         varname = self.getname_u(varindex)

File pypy/module/__builtin__/__init__.py

 
         'compile'       : 'compiling.compile',
         'eval'          : 'compiling.eval',
+        '__build_class__': 'compiling.build_class',
 
         '__import__'    : 'pypy.module.imp.importing.importhook',
         'reload'        : 'pypy.module.imp.importing.reload',

File pypy/module/__builtin__/compiling.py

 from pypy.interpreter.error import OperationError
 from pypy.interpreter.astcompiler import consts, ast
 from pypy.interpreter.gateway import unwrap_spec
+from pypy.interpreter.argument import Arguments
+from pypy.interpreter.nestedscope import Cell
 
 @unwrap_spec(filename=str, mode=str, flags=int, dont_inherit=int)
 def compile(space, w_source, filename, mode, flags=0, dont_inherit=0):
             space.setitem(w_globals, space.wrap('__builtins__'), w_builtin)
 
     return codeobj.exec_code(space, w_globals, w_locals)
+
+def build_class(space, w_func, w_name, __args__):
+    bases_w, kwds_w = __args__.unpack()
+    w_bases = space.newtuple(bases_w)
+    w_meta = kwds_w.pop('metaclass', None)
+    if w_meta is None:
+        if bases_w:
+            w_meta = space.type(bases_w[0])
+        else:
+            w_meta = space.w_type
+    
+    try:
+        w_prep = space.getattr(w_meta, space.wrap("__prepare__"))
+    except OperationError, e:
+        if not e.match(space, space.w_AttributeError):
+            raise
+        w_namespace = space.newdict()
+    else:
+        args = Arguments(space, 
+                         args_w=[w_name, w_bases],
+                         keywords=kwds_w.keys(),
+                         keywords_w=kwds_w.values())
+        w_namespace = space.call_args(w_prep, args)
+    w_cell = space.call_function(w_func, w_namespace)
+    args = Arguments(space,
+                     args_w=[w_name, w_bases, w_namespace],
+                     keywords=kwds_w.keys(),
+                     keywords_w=kwds_w.values())
+    w_class = space.call_args(w_meta, args)
+    if isinstance(w_cell, Cell):
+        w_cell.set(w_class)
+    return w_class