Skip to content

bpo-34595: WIP: Type fully qualified name #9251

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions Include/ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,14 @@ PyAPI_FUNC(void) _PyEval_SignalAsyncExc(void);
#endif

/* Masks and values used by FORMAT_VALUE opcode. */
#define FVC_MASK 0x3
#define FVC_MASK 0x7
#define FVC_NONE 0x0
#define FVC_STR 0x1
#define FVC_REPR 0x2
#define FVC_ASCII 0x3
#define FVS_MASK 0x4
#define FVS_HAVE_SPEC 0x4
#define FVC_TYPE_FQN 0x4
#define FVS_MASK 0x8
#define FVS_HAVE_SPEC 0x8

#ifdef __cplusplus
}
Expand Down
2 changes: 2 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ PyAPI_FUNC(PyObject *) PyType_GenericNew(PyTypeObject *,
PyObject *, PyObject *);
#ifndef Py_LIMITED_API
PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *);
PyAPI_FUNC(PyObject*) _PyType_FQN(PyTypeObject *);
PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
PyAPI_FUNC(PyObject *) _PyType_LookupId(PyTypeObject *, _Py_Identifier *);
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, _Py_Identifier *);
Expand All @@ -525,6 +526,7 @@ PyAPI_FUNC(void) _PyObject_Dump(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_Repr(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_Str(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_ASCII(PyObject *);
PyAPI_FUNC(PyObject*) _PyObject_TypeFQN(PyObject *v);
PyAPI_FUNC(PyObject *) PyObject_Bytes(PyObject *);
PyAPI_FUNC(PyObject *) PyObject_RichCompare(PyObject *, PyObject *, int);
PyAPI_FUNC(int) PyObject_RichCompareBool(PyObject *, PyObject *, int);
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,7 @@ def test_new_type(self):
self.assertEqual(A.__name__, 'A')
self.assertEqual(A.__qualname__, 'A')
self.assertEqual(A.__module__, __name__)
self.assertEqual(A.__fqn__, f'{__name__}.A')
self.assertEqual(A.__bases__, (object,))
self.assertIs(A.__base__, object)
x = A()
Expand Down Expand Up @@ -1907,6 +1908,12 @@ def test_type_qualname(self):
A.__qualname__ = b'B'
self.assertEqual(A.__qualname__, 'D.E')

def test_type_fqn(self):
A = type('A', (), {})
self.assertEqual(A.__fqn__, f'{__name__}.A')
with self.assertRaises(AttributeError):
A.__fqn__ = "B"

def test_type_doc(self):
for doc in 'x', '\xc4', '\U0001f40d', 'x\x00y', b'x', 42, None:
A = type('A', (), {'__doc__': doc})
Expand Down
29 changes: 28 additions & 1 deletion Objects/clinic/typeobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,13 @@ PyObject_ASCII(PyObject *v)
return res;
}

PyObject*
_PyObject_TypeFQN(PyObject *v)
{
PyTypeObject *type = Py_TYPE(v);
return _PyType_FQN(type);
}

PyObject *
PyObject_Bytes(PyObject *v)
{
Expand Down
2 changes: 2 additions & 0 deletions Objects/stringlib/unicode_format.h
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,8 @@ do_conversion(PyObject *obj, Py_UCS4 conversion)
return PyObject_Str(obj);
case 'a':
return PyObject_ASCII(obj);
case 't':
return _PyObject_TypeFQN(obj);
default:
if (conversion > 32 && conversion < 127) {
/* It's the ASCII subrange; casting to char is safe
Expand Down
112 changes: 90 additions & 22 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ _PyType_Name(PyTypeObject *type)
return s;
}


static PyObject *
type_name(PyTypeObject *type, void *context)
{
Expand All @@ -417,7 +418,7 @@ type_name(PyTypeObject *type, void *context)
}

static PyObject *
type_qualname(PyTypeObject *type, void *context)
_PyType_QualName(PyTypeObject *type)
{
if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) {
PyHeapTypeObject* et = (PyHeapTypeObject*)type;
Expand All @@ -429,6 +430,12 @@ type_qualname(PyTypeObject *type, void *context)
}
}

static PyObject *
type_qualname(PyTypeObject *type, void *context)
{
return _PyType_QualName(type);
}

static int
type_set_name(PyTypeObject *type, PyObject *value, void *context)
{
Expand Down Expand Up @@ -481,7 +488,7 @@ type_set_qualname(PyTypeObject *type, PyObject *value, void *context)
}

static PyObject *
type_module(PyTypeObject *type, void *context)
_PyType_Module(PyTypeObject *type)
{
PyObject *mod;

Expand Down Expand Up @@ -509,6 +516,12 @@ type_module(PyTypeObject *type, void *context)
return mod;
}

static PyObject *
type_module(PyTypeObject *type, void *context)
{
return _PyType_Module(type);
}

static int
type_set_module(PyTypeObject *type, PyObject *value, void *context)
{
Expand All @@ -520,6 +533,45 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
return _PyDict_SetItemId(type->tp_dict, &PyId___module__, value);
}

PyObject*
_PyType_FQN(PyTypeObject *type)
{
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
/* For static types, tp_name is already the fully qualified name */
return PyUnicode_FromString(type->tp_name);
}

PyObject *module = _PyType_Module(type);
if (module == NULL) {
/* Ignore AttributeError */
PyErr_Clear();
}
else if (!PyUnicode_Check(module)) {
Py_CLEAR(module);
}

PyHeapTypeObject* et = (PyHeapTypeObject*)type;
PyObject *qualname = et->ht_qualname; /* borrowed reference */

PyObject *fullname;
if (module != NULL && !_PyUnicode_EqualToASCIIId(module, &PyId_builtins)) {
fullname = PyUnicode_FromFormat("%U.%U", module, qualname);
}
else {
Py_INCREF(qualname);
fullname = qualname;
}
Py_DECREF(module);
return fullname;
}

static PyObject *
type_fqn(PyTypeObject *type, void *context)
{
return _PyType_FQN(type);
}


static PyObject *
type_abstractmethods(PyTypeObject *type, void *context)
{
Expand Down Expand Up @@ -869,6 +921,7 @@ type___subclasscheck___impl(PyTypeObject *self, PyObject *subclass)
static PyGetSetDef type_getsets[] = {
{"__name__", (getter)type_name, (setter)type_set_name, NULL},
{"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL},
{"__fqn__", (getter)type_fqn, (setter)NULL, NULL},
{"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL},
{"__module__", (getter)type_module, (setter)type_set_module, NULL},
{"__abstractmethods__", (getter)type_abstractmethods,
Expand All @@ -882,28 +935,15 @@ static PyGetSetDef type_getsets[] = {
static PyObject *
type_repr(PyTypeObject *type)
{
PyObject *mod, *name, *rtn;
PyObject *fqn, *rtn;

mod = type_module(type, NULL);
if (mod == NULL)
PyErr_Clear();
else if (!PyUnicode_Check(mod)) {
Py_DECREF(mod);
mod = NULL;
}
name = type_qualname(type, NULL);
if (name == NULL) {
Py_XDECREF(mod);
fqn = _PyType_FQN(type);
if (fqn == NULL) {
return NULL;
}

if (mod != NULL && !_PyUnicode_EqualToASCIIId(mod, &PyId_builtins))
rtn = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
else
rtn = PyUnicode_FromFormat("<class '%s'>", type->tp_name);

Py_XDECREF(mod);
Py_DECREF(name);
rtn = PyUnicode_FromFormat("<class '%U'>", fqn);
Py_DECREF(fqn);
return rtn;
}

Expand Down Expand Up @@ -3451,6 +3491,33 @@ type___sizeof___impl(PyTypeObject *self)
return PyLong_FromSsize_t(size);
}

/*[clinic input]
type.__format__

format_spec: unicode
/

Default type formatter.
[clinic start generated code]*/

static PyObject *
type___format___impl(PyTypeObject *self, PyObject *format_spec)
/*[clinic end generated code: output=01d6933085f07a7c input=6a87e32a4dcf0cb1]*/
{
if (PyUnicode_GET_LENGTH(format_spec) == 0) {
return PyObject_Str((PyObject *)self);
}
else if (_PyUnicode_EqualToASCIIString(format_spec, "T")) {
return _PyType_FQN(self);
}
else {
PyErr_Format(PyExc_TypeError,
"unsupported format string passed to %.200s.__format__",
self->tp_name);
return NULL;
}
}

static PyMethodDef type_methods[] = {
TYPE_MRO_METHODDEF
TYPE___SUBCLASSES___METHODDEF
Expand All @@ -3462,6 +3529,7 @@ static PyMethodDef type_methods[] = {
TYPE___SUBCLASSCHECK___METHODDEF
TYPE___DIR___METHODDEF
TYPE___SIZEOF___METHODDEF
TYPE___FORMAT___METHODDEF
{0}
};

Expand Down Expand Up @@ -3741,14 +3809,14 @@ object_repr(PyObject *self)
PyObject *mod, *name, *rtn;

type = Py_TYPE(self);
mod = type_module(type, NULL);
mod = _PyType_Module(type);
if (mod == NULL)
PyErr_Clear();
else if (!PyUnicode_Check(mod)) {
Py_DECREF(mod);
mod = NULL;
}
name = type_qualname(type, NULL);
name = _PyType_QualName(type);
if (name == NULL) {
Py_XDECREF(mod);
return NULL;
Expand Down
2 changes: 1 addition & 1 deletion Python/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -4608,7 +4608,7 @@ fstring_find_expr(const char **str, const char *end, int raw, int recurse_lvl,

/* Validate the conversion. */
if (!(conversion == 's' || conversion == 'r'
|| conversion == 'a')) {
|| conversion == 'a' || conversion == 't')) {
ast_error(c, n, "f-string: invalid conversion character: "
"expected 's', 'r', or 'a'");
return -1;
Expand Down
1 change: 1 addition & 0 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -3344,6 +3344,7 @@ _PyEval_EvalFrameDefault(PyFrameObject *f, int throwflag)
case FVC_STR: conv_fn = PyObject_Str; break;
case FVC_REPR: conv_fn = PyObject_Repr; break;
case FVC_ASCII: conv_fn = PyObject_ASCII; break;
case FVC_TYPE_FQN: conv_fn = _PyObject_TypeFQN; break;

/* Must be 0 (meaning no conversion), since only four
values are allowed by (oparg & FVC_MASK). */
Expand Down
2 changes: 2 additions & 0 deletions Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -3677,6 +3677,7 @@ compiler_formatted_value(struct compiler *c, expr_ty e)
!s : 001 0x1 FVC_STR
!r : 010 0x2 FVC_REPR
!a : 011 0x3 FVC_ASCII
!t : 100 0x4 FVC_TYPE_FQN

next bit is whether or not we have a format spec:
yes : 100 0x4
Expand All @@ -3692,6 +3693,7 @@ compiler_formatted_value(struct compiler *c, expr_ty e)
case 's': oparg = FVC_STR; break;
case 'r': oparg = FVC_REPR; break;
case 'a': oparg = FVC_ASCII; break;
case 't': oparg = FVC_TYPE_FQN; break;
case -1: oparg = FVC_NONE; break;
default:
PyErr_SetString(PyExc_SystemError,
Expand Down