diff --git a/Lib/annotationlib.py b/Lib/annotationlib.py index 8204c762cce8a2b..07543df2a681afc 100644 --- a/Lib/annotationlib.py +++ b/Lib/annotationlib.py @@ -1090,9 +1090,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 5087c3ca425f1fc..9158673acc82925 100644 --- a/Lib/test/test_annotationlib.py +++ b/Lib/test/test_annotationlib.py @@ -1850,6 +1850,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.