From d18daec001d60544bc6513c3e1a3178d4f43e5a2 Mon Sep 17 00:00:00 2001 From: Artem Yurchenko Date: Mon, 9 Sep 2024 14:33:35 -0700 Subject: [PATCH 1/2] fix unexpected '__doc__' values some '__doc__' fields of standard library symbols (e.g. WrapperDescriptorType.__doc__) don't return a string, they return a 'getset_descriptor'. Thus, an attempt to print "as string" fails. The solution is to check that __doc__ is an instance of str. Note that it wasn't uncovered by the tests due to classes not being attached to their parent in some cases. This is be done in one of the subsequent commits. it's a part of the campaign to get rid of non-module roots --- astroid/nodes/node_classes.py | 11 ++++++++++- astroid/raw_building.py | 15 +++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 9a0ec1beb5..c359fcddf8 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -2061,7 +2061,16 @@ def __init__( :param end_col_offset: The end column this node appears on in the source code. Note: This is after the last symbol. """ - self.value: Any = value + if getattr(value, "__name__", None) == "__doc__": + warnings.warn( # pragma: no cover + "You have most likely called a __doc__ field of some object " + "and it didn't return a string. " + "That happens to some symbols from the standard library. " + "Check for isinstance(.__doc__, str).", + RuntimeWarning, + stacklevel=0, + ) + self.value = value """The value that the constant represents.""" self.kind: str | None = kind # can be None diff --git a/astroid/raw_building.py b/astroid/raw_building.py index a89a87b571..d65e3762cb 100644 --- a/astroid/raw_building.py +++ b/astroid/raw_building.py @@ -56,7 +56,8 @@ def _add_dunder_class(func, member) -> None: if not cls_name: return cls_bases = [ancestor.__name__ for ancestor in python_cls.__bases__] - ast_klass = build_class(cls_name, cls_bases, python_cls.__doc__) + doc = python_cls.__doc__ if isinstance(python_cls.__doc__, str) else None + ast_klass = build_class(cls_name, cls_bases, doc) func.instance_attrs["__class__"] = [ast_klass] @@ -316,7 +317,7 @@ def object_build_function( args, posonlyargs, defaults, - member.__doc__, + member.__doc__ if isinstance(member.__doc__, str) else None, kwonlyargs=kwonlyargs, kwonlydefaults=kwonly_defaults, ) @@ -357,11 +358,8 @@ def _base_class_object_build( """ class_name = name or getattr(member, "__name__", None) or localname assert isinstance(class_name, str) - klass = build_class( - class_name, - basenames, - member.__doc__, - ) + doc = member.__doc__ if isinstance(member.__doc__, str) else None + klass = build_class(class_name, basenames, doc) klass._newstyle = isinstance(member, type) node.add_local_node(klass, localname) try: @@ -718,11 +716,12 @@ def _astroid_bootstrapping() -> None: parent=nodes.Unknown(), ) klass.parent = astroid_builtin + doc = _type.__doc__ if isinstance(_type.__doc__, str) else None klass.postinit( bases=[], body=[], decorators=None, - doc_node=nodes.Const(value=_type.__doc__) if _type.__doc__ else None, + doc_node=nodes.Const(doc) if doc else None, ) builder.object_build(klass, _type) astroid_builtin[_type.__name__] = klass From 188b5d96915a9a27ac3a568b463d80ce254cbaf4 Mon Sep 17 00:00:00 2001 From: Artem Yurchenko Date: Mon, 9 Sep 2024 15:08:30 -0700 Subject: [PATCH 2/2] put the "temporary_class" for the metaclass hack into adhoc module it's a part of the campaign to get rid of non-module roots --- astroid/nodes/scoped_nodes/scoped_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/astroid/nodes/scoped_nodes/scoped_nodes.py b/astroid/nodes/scoped_nodes/scoped_nodes.py index 5aed80869e..bd6c3e9d07 100644 --- a/astroid/nodes/scoped_nodes/scoped_nodes.py +++ b/astroid/nodes/scoped_nodes/scoped_nodes.py @@ -1624,7 +1624,7 @@ def infer_call_result( col_offset=0, end_lineno=0, end_col_offset=0, - parent=self, + parent=AstroidManager().adhoc_module, ) new_class.hide = True new_class.postinit(