Author: Amaury Forgeot d'Arc <[email protected]>
Branch: py3k
Changeset: r47953:444ebc97d236
Date: 2011-10-12 00:13 +0200
http://bitbucket.org/pypy/pypy/changeset/444ebc97d236/
Log: 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)
diff --git a/pypy/interpreter/astcompiler/assemble.py
b/pypy/interpreter/astcompiler/assemble.py
--- a/pypy/interpreter/astcompiler/assemble.py
+++ b/pypy/interpreter/astcompiler/assemble.py
@@ -540,6 +540,8 @@
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,
diff --git a/pypy/interpreter/astcompiler/astbuilder.py
b/pypy/interpreter/astcompiler/astbuilder.py
--- a/pypy/interpreter/astcompiler/astbuilder.py
+++ b/pypy/interpreter/astcompiler/astbuilder.py
@@ -460,17 +460,25 @@
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:
diff --git a/pypy/interpreter/astcompiler/codegen.py
b/pypy/interpreter/astcompiler/codegen.py
--- a/pypy/interpreter/astcompiler/codegen.py
+++ b/pypy/interpreter/astcompiler/codegen.py
@@ -313,17 +313,23 @@
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):
@@ -907,22 +913,22 @@
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:
@@ -934,6 +940,15 @@
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
@@ -1242,8 +1257,23 @@
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)
diff --git a/pypy/interpreter/pyopcode.py b/pypy/interpreter/pyopcode.py
--- a/pypy/interpreter/pyopcode.py
+++ b/pypy/interpreter/pyopcode.py
@@ -511,6 +511,9 @@
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()
@@ -550,16 +553,13 @@
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)
diff --git a/pypy/module/__builtin__/__init__.py
b/pypy/module/__builtin__/__init__.py
--- a/pypy/module/__builtin__/__init__.py
+++ b/pypy/module/__builtin__/__init__.py
@@ -77,6 +77,7 @@
'compile' : 'compiling.compile',
'eval' : 'compiling.eval',
+ '__build_class__': 'compiling.build_class',
'__import__' : 'pypy.module.imp.importing.importhook',
'reload' : 'pypy.module.imp.importing.reload',
diff --git a/pypy/module/__builtin__/compiling.py
b/pypy/module/__builtin__/compiling.py
--- a/pypy/module/__builtin__/compiling.py
+++ b/pypy/module/__builtin__/compiling.py
@@ -6,6 +6,8 @@
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):
@@ -107,3 +109,35 @@
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
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit