diff --git a/Include/ceval.h b/Include/ceval.h index bce8a0beed85e1..afc3e9ba6bbb96 100644 --- a/Include/ceval.h +++ b/Include/ceval.h @@ -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 } diff --git a/Include/object.h b/Include/object.h index c772deaf57dbb5..98bf3d1a8c89f8 100644 --- a/Include/object.h +++ b/Include/object.h @@ -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 *); @@ -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); diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 233c579356530c..e591ac592f6070 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -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() @@ -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}) diff --git a/Objects/clinic/typeobject.c.h b/Objects/clinic/typeobject.c.h index 2760065ef33f7f..68b4fb439f3c15 100644 --- a/Objects/clinic/typeobject.c.h +++ b/Objects/clinic/typeobject.c.h @@ -130,6 +130,33 @@ type___sizeof__(PyTypeObject *self, PyObject *Py_UNUSED(ignored)) return type___sizeof___impl(self); } +PyDoc_STRVAR(type___format____doc__, +"__format__($self, format_spec, /)\n" +"--\n" +"\n" +"Default type formatter."); + +#define TYPE___FORMAT___METHODDEF \ + {"__format__", (PyCFunction)type___format__, METH_O, type___format____doc__}, + +static PyObject * +type___format___impl(PyTypeObject *self, PyObject *format_spec); + +static PyObject * +type___format__(PyTypeObject *self, PyObject *arg) +{ + PyObject *return_value = NULL; + PyObject *format_spec; + + if (!PyArg_Parse(arg, "U:__format__", &format_spec)) { + goto exit; + } + return_value = type___format___impl(self, format_spec); + +exit: + return return_value; +} + PyDoc_STRVAR(object___reduce____doc__, "__reduce__($self, /)\n" "--\n" @@ -237,4 +264,4 @@ object___dir__(PyObject *self, PyObject *Py_UNUSED(ignored)) { return object___dir___impl(self); } -/*[clinic end generated code: output=8c4c856859564eaa input=a9049054013a1b77]*/ +/*[clinic end generated code: output=74c57a0184032856 input=a9049054013a1b77]*/ diff --git a/Objects/object.c b/Objects/object.c index 6498756c92bd0d..8b2f3114c94864 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -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) { diff --git a/Objects/stringlib/unicode_format.h b/Objects/stringlib/unicode_format.h index baaac811a56ed0..a190d9176388f5 100644 --- a/Objects/stringlib/unicode_format.h +++ b/Objects/stringlib/unicode_format.h @@ -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 diff --git a/Objects/typeobject.c b/Objects/typeobject.c index b9e69bf1bd1d64..7d529069525c41 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -402,6 +402,7 @@ _PyType_Name(PyTypeObject *type) return s; } + static PyObject * type_name(PyTypeObject *type, void *context) { @@ -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; @@ -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) { @@ -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; @@ -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) { @@ -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) { @@ -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, @@ -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("", mod, name); - else - rtn = PyUnicode_FromFormat("", type->tp_name); - - Py_XDECREF(mod); - Py_DECREF(name); + rtn = PyUnicode_FromFormat("", fqn); + Py_DECREF(fqn); return rtn; } @@ -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 @@ -3462,6 +3529,7 @@ static PyMethodDef type_methods[] = { TYPE___SUBCLASSCHECK___METHODDEF TYPE___DIR___METHODDEF TYPE___SIZEOF___METHODDEF + TYPE___FORMAT___METHODDEF {0} }; @@ -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; diff --git a/Python/ast.c b/Python/ast.c index 94962e00c7d33e..e4cdc293f33084 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -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; diff --git a/Python/ceval.c b/Python/ceval.c index f3a74b00a2b64a..bf042c2f3c0d77 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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). */ diff --git a/Python/compile.c b/Python/compile.c index 3528670ef67503..5d794b17542828 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -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 @@ -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,