From b800d6d92af54dd05db71cd7261408941bab5d00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?tonghuaroot=20=28=E7=AB=A5=E8=AF=9D=29?= Date: Tue, 13 Oct 2026 07:37:58 +0800 Subject: [PATCH] gh-152692: Fix annotationlib.type_repr() for bound built-in methods (GH-152693) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit type_repr() returned a string naming a nonexistent module None -- e.g. type_repr([].append) == 'None.list.append' -- for bound built-in methods, whose __module__ is None. Return the qualified name when __module__ is None, as already done for the "builtins" module. (cherry picked from commit 6979003a1d73383ce798eb1301fe93d4d74b1903) Co-authored-by: tonghuaroot (童话) --- Lib/annotationlib.py | 12 ++++++++++-- Lib/test/test_annotationlib.py | 12 ++++++++++++ .../2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst | 4 ++++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 5c9a0812646f814..c2b6470b26284ff 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -1113,9 +1113,17 @@ def type_repr(value): """ if isinstance(value, (type, types.FunctionType, types.BuiltinFunctionType)): - if value.__module__ == "builtins": + module = value.__module__ + if module is None: + # Built-in methods have __module__ set to None; recover the module + # from the bound object (the class itself for class-bound methods). + obj = getattr(value, "__self__", None) + if obj is not None: + cls = obj if isinstance(obj, type) else type(obj) + module = cls.__module__ + if module is None or module == "builtins": return value.__qualname__ - return f"{value.__module__}.{value.__qualname__}" + return f"{module}.{value.__qualname__}" elif isinstance(value, _Template): tree = _template_to_ast(value) return ast.unparse(tree) diff --git a/Lib/test/test_annotationlib.py b/Lib/test/test_annotationlib.py index 7db6f8745ac84ff..f5b226f3e205c4f 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -1857,6 +1857,18 @@ def nested(): type_repr(nested), f"{__name__}.TestTypeRepr.test_type_repr..nested" ) self.assertEqual(type_repr(len), "len") + # gh-152692: built-in methods have __module__ set to None + self.assertEqual(type_repr([].append), "list.append") + self.assertEqual(type_repr((1).bit_length), "int.bit_length") + self.assertEqual(type_repr(dict.fromkeys), "dict.fromkeys") + # Methods of extension types also have __module__ set to None, but + # are not in the builtins module. + import datetime + self.assertEqual( + type_repr(datetime.datetime(2000, 1, 1).isoformat), + "datetime.datetime.isoformat", + ) + self.assertEqual(type_repr(datetime.date.today), "datetime.date.today") self.assertEqual(type_repr(type_repr), "annotationlib.type_repr") self.assertEqual(type_repr(times_three), f"{__name__}.times_three") self.assertEqual(type_repr(...), "...") diff --git a/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst b/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst new file mode 100644 index 000000000000000..d92011f2c5acd7d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-30-15-00-00.gh-issue-152692.tr3pr0.rst @@ -0,0 +1,4 @@ +Fix :func:`annotationlib.type_repr` returning a string such as +``None.list.append`` for bound built-in methods, whose ``__module__`` is +``None``. It now returns the qualified name, as it already did for other +builtins.