From 2e3d4f439df79e514bef85a0f50cf3ce96278e2b Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Fri, 27 Mar 2026 06:30:15 +0900 Subject: [PATCH] fix --- pyrefly/lib/lsp/wasm/hover.rs | 36 ++++++++++++++++++++++++++++++++++- pyrefly/lib/test/lsp/hover.rs | 28 +++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/pyrefly/lib/lsp/wasm/hover.rs b/pyrefly/lib/lsp/wasm/hover.rs index 01e1f57ae0..651a7d098d 100644 --- a/pyrefly/lib/lsp/wasm/hover.rs +++ b/pyrefly/lib/lsp/wasm/hover.rs @@ -9,6 +9,7 @@ use std::collections::HashMap; +use dupe::Dupe; use lsp_types::Hover; use lsp_types::HoverContents; use lsp_types::MarkupContent; @@ -315,6 +316,39 @@ fn identifier_text_at( .map(|id| id.identifier.id.to_string()) } +fn docstring_for_class_object_type( + transaction: &Transaction<'_>, + handle: &Handle, + type_: &Type, +) -> Option { + let qname = match type_ { + Type::ClassDef(cls) => cls.qname(), + Type::Type(inner) => match inner.as_ref() { + Type::ClassType(cls) => cls.qname(), + _ => return None, + }, + _ => return None, + }; + let definition_handle = Handle::new( + qname.module_name(), + qname.module_path().dupe(), + handle.sys_info().dupe(), + ); + let definition = transaction + .find_definition( + &definition_handle, + qname.range().start(), + FindPreference { + resolve_call_dunders: false, + ..Default::default() + }, + ) + .ok()? + .into_iter() + .find(|item| item.definition_range == qname.range())?; + Some(Docstring(definition.docstring_range?, definition.module)) +} + fn collect_typed_dict_fields_for_hover<'a>( solver: &AnswersSolver>, ty: &Type, @@ -636,7 +670,7 @@ pub fn get_hover( let docstring = if let (Some(docstring), Some(module)) = (docstring_range, module) { Some(Docstring(docstring, module)) } else { - None + docstring_for_class_object_type(transaction, handle, &type_) }; let mut parameter_doc = keyword_argument_documentation(transaction, handle, position) diff --git a/pyrefly/lib/test/lsp/hover.rs b/pyrefly/lib/test/lsp/hover.rs index 813c78f85d..8a0130899c 100644 --- a/pyrefly/lib/test/lsp/hover.rs +++ b/pyrefly/lib/test/lsp/hover.rs @@ -1029,6 +1029,34 @@ Widget docstring"# ); } +#[test] +fn hover_on_property_returning_class_shows_class_docstring() { + let lib = r#" +from typing import Type + +class current_timestamp: + """The CURRENT_TIMESTAMP() SQL function.""" + def __init__(self, *args: object, **kwargs: object) -> None: ... + +class Func: + @property + def current_timestamp(self) -> Type[current_timestamp]: ... +"#; + let code = r#" +from lib import Func + +f = Func() +f.current_timestamp +# ^ +"#; + let report = + get_batched_lsp_operations_report(&[("main", code), ("lib", lib)], get_test_report); + assert!( + report.contains("The CURRENT_TIMESTAMP() SQL function."), + "Expected hover to show the class docstring, got: {report}" + ); +} + #[test] fn hover_on_first_component_of_multi_part_import() { let mymod_init = r#"# mymod/__init__.py