Follow parameter annotations in REPL type documentation; support enums

Referenced types are now also discovered through the parameter annotations of type members and
facade methods (not only through return types), such that e.g. SymbolKind (a parameter type of
LanguageServerSymbol.iter_ancestors) is documented along with LanguageServerSymbol. Enums are
documented with their members and values. The lsp facade declares SymbolKind as a referenced type.
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent 24b41dcb88
commit 8d1fb7424c
3 files changed
+44 -13

No files matched your search

+7 -6
View File
@@ -331,6 +331,7 @@ class LspApi(FacadeApi):
ReferencedType(LspReferenceCollection),
ReferencedType(ReferenceInLanguageServerSymbol),
ReferencedType(LspDiagnostics),
ReferencedType(SymbolKind),
],
)
@@ -453,8 +454,8 @@ class LspApi(FacadeApi):
:param include_info: whether to include additional info (hover-like, typically including docstring and signature),
about the symbol (ignored if include_body is True). Info is never included for child symbols.
Note: Depending on the language, this can be slow (e.g., C/C++).
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
:param exclude_kinds: (optional) list of LSP symbol kinds (integers) to exclude.
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
:param exclude_kinds: (optional) list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
:param substring_matching: If True, use substring matching for the last segment of `name_path_pattern`
(i.e. the name of the symbol, e.g. "foo" in "Class/foo" or "my_method" in "my_method").
:param max_matches: Maximum number of permitted matches. If exceeded, an error containing a shortened result is raised,
@@ -517,8 +518,8 @@ class LspApi(FacadeApi):
:param name_path: name path of the symbol
:param relative_path: the relative path to the file containing the symbol for which to find references.
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
:param exclude_kinds: optional list of LSP symbol kinds (integers) to exclude.
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
:param exclude_kinds: optional list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
:param max_answer_chars: max result length; -1 for default
:return: the references to the symbol
"""
@@ -554,8 +555,8 @@ class LspApi(FacadeApi):
Note that here you can't pass a directory but must pass a file.
:param include_info: whether to include additional info (hover-like, typically including docstring and signature),
about the implementing symbols.
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
:param exclude_kinds: (optional) list of LSP symbol kinds (integers) to exclude.
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
:param exclude_kinds: (optional) list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
:param max_answer_chars: max result length; -1 for default
:return: the symbols implementing the given symbol
"""
+33 -7
View File
@@ -11,6 +11,7 @@ import typing
from abc import ABC
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, Any, TypeVar
from serena.config.serena_config import ApiInclusionDefinition
@@ -84,8 +85,10 @@ class ReferencedType:
def get_referenced_type_names(self) -> list[str]:
"""
:return: the names of the types appearing in the annotations of the described members (attributes, properties,
method return types), in order of appearance (each name at most once, excluding the type itself)
method parameters and return types), in order of appearance (each name at most once, excluding the type itself)
"""
if self.is_enum():
return []
annotations: list[str] = []
type_hints = typing.get_type_hints(self.cls)
for member_name in self._get_member_names():
@@ -93,7 +96,7 @@ class ReferencedType:
if isinstance(member, property) and member.fget is not None:
annotations.append(format_annotation(inspect.signature(member.fget).return_annotation))
elif inspect.isfunction(member):
annotations.append(format_annotation(inspect.signature(member).return_annotation))
annotations.append(format_signature(member))
elif member_name in type_hints:
annotations.append(format_annotation(type_hints[member_name]))
names: list[str] = []
@@ -103,6 +106,16 @@ class ReferencedType:
names.append(name)
return names
def is_enum(self) -> bool:
return isinstance(self.cls, type) and issubclass(self.cls, Enum)
def _describe_enum(self) -> str:
parts = [f"enum {self.name}"]
if self.cls.__doc__:
parts.append(f" {inspect.cleandoc(self.cls.__doc__).replace(chr(10), chr(10) + ' ')}")
parts.append("members:\n" + "\n".join(f" {self.name}.{member.name} = {member.value!r}" for member in self.cls)) # type: ignore[attr-defined]
return "\n".join(parts) + "\n"
@staticmethod
def _first_doc_line(obj: Any) -> str:
doc = inspect.getdoc(obj) or ""
@@ -112,8 +125,10 @@ class ReferencedType:
def describe(self) -> str:
"""
:return: the type's documentation: its docstring, attributes/properties with their types and methods with their
signatures and documentation
signatures and documentation; for enums, the members with their values
"""
if self.is_enum():
return self._describe_enum()
attributes: list[str] = []
methods: list[str] = []
type_hints = typing.get_type_hints(self.cls)
@@ -325,7 +340,17 @@ class FacadeMethod:
:return: the types referenced by the facade which appear in the method's return type annotation
"""
return_annotation = format_annotation(inspect.signature(self._implementation).return_annotation)
return [t for t in self.parent.get_types() if re.search(rf"\b{re.escape(t.name)}\b", return_annotation)]
return self._find_referenced_types(return_annotation)
def get_referenced_parameter_types(self) -> list[ReferencedType]:
"""
:return: the types referenced by the facade which appear in the annotations of the method's parameters
"""
parameters = inspect.signature(self._implementation).parameters.values()
return self._find_referenced_types(" ".join(format_annotation(p.annotation) for p in parameters))
def _find_referenced_types(self, annotation_text: str) -> list[ReferencedType]:
return [t for t in self.parent.get_types() if re.search(rf"\b{re.escape(t.name)}\b", annotation_text)]
def get_summary(self) -> str:
"""
@@ -336,16 +361,17 @@ class FacadeMethod:
def describe(self) -> str:
"""
:return: the method's signature and documentation, with a pointer to the documentation of its return type
if it is a type referenced by the facade
:return: the method's signature and documentation, with pointers to the documentation of the referenced types
appearing in its return type and parameter annotations
"""
signature = format_signature(self._implementation)
doc = inspect.getdoc(self._implementation) or "(no documentation)"
text = f"{self.qualified_name}{signature}\n{doc}\n"
referenced = self.get_referenced_return_types()
referenced += [t for t in self.get_referenced_parameter_types() if t not in referenced]
if referenced:
pointers = ", ".join(f'`s.info("{t.name}")`' for t in referenced)
text += f"Return type documentation: {pointers}\n"
text += f"Type documentation: {pointers}\n"
return text
def describe_summary(self) -> str:
+4
View File
@@ -72,6 +72,10 @@ class TestReplExecution:
assert type_info == repl.execute('s.info("LanguageServerSymbol")')
assert "get_name_path() -> str" in type_info and "iter_children()" in type_info
assert "to_dict" not in type_info # not among the curated members
# types referenced by parameters are documented as well; enums are documented with their members
assert "enum SymbolKind" in type_info and "SymbolKind.Class = 5" in type_info # via iter_ancestors(up_to_symbol_kind)
assert "enum SymbolKind" in repl.execute('s.info("SymbolKind")')
assert "represent()" not in repl.execute('s.info("LspSymbolCollection")') # the representation mechanism is not exposed
def test_contained_types_are_documented_once_per_session(self, repl: SerenaRepl) -> None: