Skip to content

Commit 6979003

Browse files
authored
gh-152692: Fix annotationlib.type_repr() for bound built-in methods (#152693)
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.
1 parent 767e918 commit 6979003

3 files changed

Lines changed: 26 additions & 2 deletions

File tree

‎Lib/annotationlib.py‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,9 +1105,17 @@ def type_repr(value):
11051105
11061106
"""
11071107
if isinstance(value, (type, types.FunctionType, types.BuiltinFunctionType)):
1108-
if value.__module__ == "builtins":
1108+
module = value.__module__
1109+
if module is None:
1110+
# Built-in methods have __module__ set to None; recover the module
1111+
# from the bound object (the class itself for class-bound methods).
1112+
obj = getattr(value, "__self__", None)
1113+
if obj is not None:
1114+
cls = obj if isinstance(obj, type) else type(obj)
1115+
module = cls.__module__
1116+
if module is None or module == "builtins":
11091117
return value.__qualname__
1110-
return f"{value.__module__}.{value.__qualname__}"
1118+
return f"{module}.{value.__qualname__}"
11111119
elif isinstance(value, _Template):
11121120
tree = _template_to_ast(value)
11131121
return ast.unparse(tree)

‎Lib/test/test_annotationlib.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,6 +1888,18 @@ def nested():
18881888
type_repr(nested), f"{__name__}.TestTypeRepr.test_type_repr.<locals>.nested"
18891889
)
18901890
self.assertEqual(type_repr(len), "len")
1891+
# gh-152692: built-in methods have __module__ set to None
1892+
self.assertEqual(type_repr([].append), "list.append")
1893+
self.assertEqual(type_repr((1).bit_length), "int.bit_length")
1894+
self.assertEqual(type_repr(dict.fromkeys), "dict.fromkeys")
1895+
# Methods of extension types also have __module__ set to None, but
1896+
# are not in the builtins module.
1897+
import datetime
1898+
self.assertEqual(
1899+
type_repr(datetime.datetime(2000, 1, 1).isoformat),
1900+
"datetime.datetime.isoformat",
1901+
)
1902+
self.assertEqual(type_repr(datetime.date.today), "datetime.date.today")
18911903
self.assertEqual(type_repr(type_repr), "annotationlib.type_repr")
18921904
self.assertEqual(type_repr(times_three), f"{__name__}.times_three")
18931905
self.assertEqual(type_repr(...), "...")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix :func:`annotationlib.type_repr` returning a string such as
2+
``None.list.append`` for bound built-in methods, whose ``__module__`` is
3+
``None``. It now returns the qualified name, as it already did for other
4+
builtins.

0 commit comments

Comments
 (0)