diff --git a/src/serena/repl/api/lsp_api.py b/src/serena/repl/api/lsp_api.py index 15161492..7e648318 100644 --- a/src/serena/repl/api/lsp_api.py +++ b/src/serena/repl/api/lsp_api.py @@ -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 """ diff --git a/src/serena/repl/facade.py b/src/serena/repl/facade.py index ebcea4bd..eb040b06 100644 --- a/src/serena/repl/facade.py +++ b/src/serena/repl/facade.py @@ -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: diff --git a/test/serena/test_repl_tool.py b/test/serena/test_repl_tool.py index 82a8219b..2dae1c5b 100644 --- a/test/serena/test_repl_tool.py +++ b/test/serena/test_repl_tool.py @@ -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: