Skip to content

Commit

Permalink
Issue python#23722: Initialize __class__ from type.__new__()
Browse files Browse the repository at this point in the history
The __class__ cell used by zero-argument super() is now initialized
from type.__new__ rather than __build_class__, so class methods
relying on that will now work correctly when called from metaclass
methods during class creation.

Patch by Martin Teichmann.
  • Loading branch information
ncoghlan committed Sep 11, 2016
1 parent fc3f7d5 commit 944368e
Show file tree
Hide file tree
Showing 8 changed files with 1,358 additions and 1,258 deletions.
5 changes: 3 additions & 2 deletions Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,8 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.6b1 3373 (add BUILD_STRING opcode #27078)
# Python 3.6b1 3375 (add SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes
# #27985)
# Python 3.6a1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL)
# Python 3.6b1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL)
# Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722)
#
# MAGIC must change whenever the bytecode emitted by the compiler may no
# longer be understood by older implementations of the eval loop (usually
Expand All @@ -245,7 +246,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3376).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3377).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

_PYCACHE = '__pycache__'
Expand Down
81 changes: 81 additions & 0 deletions Lib/test/test_super.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,87 @@ def f():
return __class__
self.assertIs(X.f(), X)

def test___class___new(self):
test_class = None

class Meta(type):
def __new__(cls, name, bases, namespace):
nonlocal test_class
self = super().__new__(cls, name, bases, namespace)
test_class = self.f()
return self

class A(metaclass=Meta):
@staticmethod
def f():
return __class__

self.assertIs(test_class, A)

def test___class___delayed(self):
test_namespace = None

class Meta(type):
def __new__(cls, name, bases, namespace):
nonlocal test_namespace
test_namespace = namespace
return None

class A(metaclass=Meta):
@staticmethod
def f():
return __class__

self.assertIs(A, None)

B = type("B", (), test_namespace)
self.assertIs(B.f(), B)

def test___class___mro(self):
test_class = None

class Meta(type):
def mro(self):
# self.f() doesn't work yet...
self.__dict__["f"]()
return super().mro()

class A(metaclass=Meta):
def f():
nonlocal test_class
test_class = __class__

self.assertIs(test_class, A)

def test___classcell___deleted(self):
class Meta(type):
def __new__(cls, name, bases, namespace):
del namespace['__classcell__']
return super().__new__(cls, name, bases, namespace)

class A(metaclass=Meta):
@staticmethod
def f():
__class__

with self.assertRaises(NameError):
A.f()

def test___classcell___reset(self):
class Meta(type):
def __new__(cls, name, bases, namespace):
namespace['__classcell__'] = 0
return super().__new__(cls, name, bases, namespace)

class A(metaclass=Meta):
@staticmethod
def f():
__class__

with self.assertRaises(NameError):
A.f()
self.assertEqual(A.__classcell__, 0)

def test_obscure_super_errors(self):
def f():
super()
Expand Down
5 changes: 5 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ What's New in Python 3.6.0 beta 1
Core and Builtins
-----------------

- Issue #23722: The __class__ cell used by zero-argument super() is now
initialized from type.__new__ rather than __build_class__, so class methods
relying on that will now work correctly when called from metaclass methods
during class creation. Patch by Martin Teichmann.

- Issue #25221: Fix corrupted result from PyLong_FromLong(0) when Python
is compiled with NSMALLPOSINTS = 0.

Expand Down
13 changes: 11 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2285,14 +2285,15 @@ static PyObject *
type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
{
PyObject *name, *bases = NULL, *orig_dict, *dict = NULL;
PyObject *qualname, *slots = NULL, *tmp, *newslots;
PyObject *qualname, *slots = NULL, *tmp, *newslots, *cell;
PyTypeObject *type = NULL, *base, *tmptype, *winner;
PyHeapTypeObject *et;
PyMemberDef *mp;
Py_ssize_t i, nbases, nslots, slotoffset, name_size;
int j, may_add_dict, may_add_weak, add_dict, add_weak;
_Py_IDENTIFIER(__qualname__);
_Py_IDENTIFIER(__slots__);
_Py_IDENTIFIER(__classcell__);

assert(args != NULL && PyTuple_Check(args));
assert(kwds == NULL || PyDict_Check(kwds));
Expand Down Expand Up @@ -2559,7 +2560,7 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
}
et->ht_qualname = qualname ? qualname : et->ht_name;
Py_INCREF(et->ht_qualname);
if (qualname != NULL && PyDict_DelItem(dict, PyId___qualname__.object) < 0)
if (qualname != NULL && _PyDict_DelItemId(dict, &PyId___qualname__) < 0)
goto error;

/* Set tp_doc to a copy of dict['__doc__'], if the latter is there
Expand Down Expand Up @@ -2685,6 +2686,14 @@ type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)
else
type->tp_free = PyObject_Del;

/* store type in class' cell */
cell = _PyDict_GetItemId(dict, &PyId___classcell__);
if (cell != NULL && PyCell_Check(cell)) {
PyCell_Set(cell, (PyObject *) type);
_PyDict_DelItemId(dict, &PyId___classcell__);
PyErr_Clear();
}

/* Initialize the rest */
if (PyType_Ready(type) < 0)
goto error;
Expand Down
2 changes: 1 addition & 1 deletion PC/launcher.c
Original file line number Diff line number Diff line change
Expand Up @@ -1089,7 +1089,7 @@ static PYC_MAGIC magic_values[] = {
{ 3190, 3230, L"3.3" },
{ 3250, 3310, L"3.4" },
{ 3320, 3351, L"3.5" },
{ 3360, 3375, L"3.6" },
{ 3360, 3379, L"3.6" },
{ 0 }
};

Expand Down
10 changes: 4 additions & 6 deletions Python/bltinmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ _Py_IDENTIFIER(stderr);
static PyObject *
builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
{
PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *cell;
PyObject *func, *name, *bases, *mkw, *meta, *winner, *prep, *ns, *none;
PyObject *cls = NULL;
Py_ssize_t nargs;
int isclass = 0; /* initialize to prevent gcc warning */
Expand Down Expand Up @@ -167,15 +167,13 @@ builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds)
Py_DECREF(bases);
return NULL;
}
cell = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
none = PyEval_EvalCodeEx(PyFunction_GET_CODE(func), PyFunction_GET_GLOBALS(func), ns,
NULL, 0, NULL, 0, NULL, 0, NULL,
PyFunction_GET_CLOSURE(func));
if (cell != NULL) {
if (none != NULL) {
PyObject *margs[3] = {name, bases, ns};
cls = _PyObject_FastCallDict(meta, margs, 3, mkw);
if (cls != NULL && PyCell_Check(cell))
PyCell_Set(cell, cls);
Py_DECREF(cell);
Py_DECREF(none);
}
Py_DECREF(ns);
Py_DECREF(meta);
Expand Down
15 changes: 10 additions & 5 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -1968,7 +1968,7 @@ compiler_class(struct compiler *c, stmt_ty s)
return 0;
}
if (c->u->u_ste->ste_needs_class_closure) {
/* return the (empty) __class__ cell */
/* store __classcell__ into class namespace */
str = PyUnicode_InternFromString("__class__");
if (str == NULL) {
compiler_exit_scope(c);
Expand All @@ -1981,15 +1981,20 @@ compiler_class(struct compiler *c, stmt_ty s)
return 0;
}
assert(i == 0);
/* Return the cell where to store __class__ */

ADDOP_I(c, LOAD_CLOSURE, i);
str = PyUnicode_InternFromString("__classcell__");
if (!str || !compiler_nameop(c, str, Store)) {
Py_XDECREF(str);
compiler_exit_scope(c);
return 0;
}
Py_DECREF(str);
}
else {
/* This happens when nobody references the cell. */
assert(PyDict_Size(c->u->u_cellvars) == 0);
/* This happens when nobody references the cell. Return None. */
ADDOP_O(c, LOAD_CONST, Py_None, consts);
}
ADDOP_IN_SCOPE(c, RETURN_VALUE);
/* create the code object */
co = assemble(c, 1);
}
Expand Down
Loading

0 comments on commit 944368e

Please sign in to comment.