From 23a81fc5da7d30b33e9727b9f2d9e1e6492f5336 Mon Sep 17 00:00:00 2001 From: Chad Dombrova Date: Sat, 8 Jun 2024 22:23:07 -0700 Subject: [PATCH] stubgen: fix FunctionContext.fullname for nested classes --- mypy/stubgen.py | 16 +++++++++++----- mypy/stubgenc.py | 10 +++++++--- mypy/stubutil.py | 16 ++++++++++++++-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/mypy/stubgen.py b/mypy/stubgen.py index 02c0c1e58ab5e..8e681ead94f0b 100755 --- a/mypy/stubgen.py +++ b/mypy/stubgen.py @@ -468,7 +468,7 @@ def __init__( self._vars: list[list[str]] = [[]] # What was generated previously in the stub file. self._state = EMPTY - self._current_class: ClassDef | None = None + self._class_stack: list[ClassDef] = [] # Was the tree semantically analysed before? self.analyzed = analyzed # Short names of methods defined in the body of the current class @@ -476,6 +476,10 @@ def __init__( self.processing_enum = False self.processing_dataclass = False + @property + def _current_class(self) -> ClassDef | None: + return self._class_stack[-1] if self._class_stack else None + def visit_mypy_file(self, o: MypyFile) -> None: self.module_name = o.fullname # Current module being processed self.path = o.path @@ -646,12 +650,14 @@ def visit_func_def(self, o: FuncDef) -> None: if init_code: self.add(init_code) - if self._current_class is not None: + if self._class_stack: if len(o.arguments): self_var = o.arguments[0].variable.name else: self_var = "self" - class_info = ClassInfo(self._current_class.name, self_var) + class_info = None + for class_def in self._class_stack: + class_info = ClassInfo(class_def.name, self_var, parent=class_info) else: class_info = None @@ -741,7 +747,7 @@ def get_fullname(self, expr: Expression) -> str: return self.resolve_name(name) def visit_class_def(self, o: ClassDef) -> None: - self._current_class = o + self._class_stack.append(o) self.method_names = find_method_names(o.defs.body) sep: int | None = None if self.is_top_level() and self._state != EMPTY: @@ -786,8 +792,8 @@ def visit_class_def(self, o: ClassDef) -> None: self._state = CLASS self.method_names = set() self.processing_dataclass = False + self._class_stack.pop(-1) self.processing_enum = False - self._current_class = None def get_base_types(self, cdef: ClassDef) -> list[str]: """Get list of base classes for a class.""" diff --git a/mypy/stubgenc.py b/mypy/stubgenc.py index 7ab500b4fe120..1cd709b9d603f 100755 --- a/mypy/stubgenc.py +++ b/mypy/stubgenc.py @@ -787,7 +787,9 @@ def get_base_types(self, obj: type) -> list[str]: bases.append(base) return [self.strip_or_import(self.get_type_fullname(base)) for base in bases] - def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> None: + def generate_class_stub( + self, class_name: str, cls: type, output: list[str], parent_class: ClassInfo | None = None + ) -> None: """Generate stub for a single class using runtime introspection. The result lines will be appended to 'output'. If necessary, any @@ -808,7 +810,9 @@ def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> self.record_name(class_name) self.indent() - class_info = ClassInfo(class_name, "", getattr(cls, "__doc__", None), cls) + class_info = ClassInfo( + class_name, "", getattr(cls, "__doc__", None), cls, parent=parent_class + ) for attr, value in items: # use unevaluated descriptors when dealing with property inspection @@ -843,7 +847,7 @@ def generate_class_stub(self, class_name: str, cls: type, output: list[str]) -> class_info, ) elif inspect.isclass(value) and self.is_defined_in_module(value): - self.generate_class_stub(attr, value, types) + self.generate_class_stub(attr, value, types, parent_class=class_info) else: attrs.append((attr, value)) diff --git a/mypy/stubutil.py b/mypy/stubutil.py index 04b36e149957d..b7a1fed7688dd 100644 --- a/mypy/stubutil.py +++ b/mypy/stubutil.py @@ -306,12 +306,18 @@ def args_str(self, args: Iterable[Type]) -> str: class ClassInfo: def __init__( - self, name: str, self_var: str, docstring: str | None = None, cls: type | None = None + self, + name: str, + self_var: str, + docstring: str | None = None, + cls: type | None = None, + parent: ClassInfo | None = None, ) -> None: self.name = name self.self_var = self_var self.docstring = docstring self.cls = cls + self.parent = parent class FunctionContext: @@ -334,7 +340,13 @@ def __init__( def fullname(self) -> str: if self._fullname is None: if self.class_info: - self._fullname = f"{self.module_name}.{self.class_info.name}.{self.name}" + parents = [] + class_info = self.class_info + while class_info is not None: + parents.append(class_info.name) + class_info = class_info.parent + namespace = ".".join(reversed(parents)) + self._fullname = f"{self.module_name}.{namespace}.{self.name}" else: self._fullname = f"{self.module_name}.{self.name}" return self._fullname