WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 447cdaf

Browse files
update based on comment
1 parent 9066433 commit 447cdaf

File tree

3 files changed

+52
-102
lines changed

3 files changed

+52
-102
lines changed

crates/pyrefly_types/src/display.rs

Lines changed: 14 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -780,9 +780,22 @@ impl Display for Type {
780780

781781
impl Type {
782782
pub fn as_hover_string(&self) -> String {
783+
self.as_hover_string_with_fallback_name(None)
784+
}
785+
786+
pub fn as_hover_string_with_fallback_name(&self, fallback_name: Option<&str>) -> String {
783787
let mut c = TypeDisplayContext::new(&[self]);
784788
c.set_display_mode_to_hover();
785-
c.display(self).to_string()
789+
let rendered = c.display(self).to_string();
790+
if let Some(name) = fallback_name
791+
&& self.is_function_type()
792+
{
793+
let trimmed = rendered.trim_start();
794+
if trimmed.starts_with('(') {
795+
return format!("def {}{}: ...", name, trimmed);
796+
}
797+
}
798+
rendered
786799
}
787800

788801
pub fn get_types_with_locations(&self) -> Vec<(String, Option<TextRangeWithModule>)> {
@@ -809,54 +822,6 @@ impl<'a> ClassDisplayContext<'a> {
809822
}
810823
}
811824

812-
/// Small helper describing the formatted code that should appear inside a hover block.
813-
///
814-
/// `body` is the snippet that will live inside the fenced code block, while
815-
/// `default_kind_label` lets callers know which `(kind)` prefix to use when the
816-
/// hover metadata can't provide one (e.g. when resolving a stub-only function).
817-
#[derive(Debug, Clone)]
818-
pub struct HoverCodeSnippet {
819-
pub body: String,
820-
pub default_kind_label: Option<&'static str>,
821-
}
822-
823-
/// Format the string returned by `Type::as_hover_string()` so that callable types
824-
/// always resemble real Python function definitions. When `name` is provided we
825-
/// will synthesize a `def name(...): ...` signature if the rendered type only
826-
/// contains the parenthesized parameter list. This keeps IDE syntax highlighting
827-
/// working even when we fall back to hover strings built from metadata alone.
828-
///
829-
/// `display` is typically `Type::as_hover_string()`, but callers may pass their
830-
/// own rendering (for example, after expanding TypedDict kwargs).
831-
pub fn format_hover_code_snippet(
832-
type_: &Type,
833-
name: Option<&str>,
834-
display: String,
835-
) -> HoverCodeSnippet {
836-
if type_.is_function_type() {
837-
let body = match name {
838-
Some(name) => {
839-
let trimmed = display.trim_start();
840-
if trimmed.starts_with('(') {
841-
format!("def {}{}: ...", name, trimmed)
842-
} else {
843-
display
844-
}
845-
}
846-
None => display,
847-
};
848-
HoverCodeSnippet {
849-
body,
850-
default_kind_label: Some("(function) "),
851-
}
852-
} else {
853-
HoverCodeSnippet {
854-
body: display,
855-
default_kind_label: None,
856-
}
857-
}
858-
}
859-
860825
#[cfg(test)]
861826
pub mod tests {
862827
use std::path::PathBuf;

pyrefly/lib/lsp/wasm/hover.rs

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ use pyrefly_types::callable::Param;
2626
use pyrefly_types::callable::ParamList;
2727
use pyrefly_types::callable::Params;
2828
use pyrefly_types::callable::Required;
29-
use pyrefly_types::display::format_hover_code_snippet;
3029
use pyrefly_types::types::BoundMethodType;
3130
use pyrefly_types::types::Forallable;
3231
use pyrefly_types::types::Type;
@@ -219,39 +218,29 @@ impl HoverValue {
219218
let cleaned = doc.trim().replace('\n', " \n");
220219
format!("{prefix}**Parameter `{}`**\n{}", name, cleaned)
221220
});
222-
let mut kind_formatted = self.kind.map_or("".to_owned(), |kind| {
223-
format!("{} ", kind.display_for_hover())
224-
});
225-
let name_formatted = self
226-
.name
227-
.as_ref()
228-
.map_or("".to_owned(), |s| format!("{s}: "));
229-
let symbol_def_formatted = if self.show_go_to_links {
230-
HoverValue::format_symbol_def_locations(&self.type_).unwrap_or("".to_owned())
231-
} else {
232-
String::new()
233-
};
234-
let type_display = self
235-
.display
236-
.clone()
237-
.unwrap_or_else(|| self.type_.as_hover_string());
238-
// Ensure callable hover bodies always contain a proper `def name(...)` so IDE syntax
239-
// highlighting stays consistent, even when metadata is missing and we fall back to
240-
// inferred identifiers.
241-
let snippet = format_hover_code_snippet(&self.type_, self.name.as_deref(), type_display);
242221
let kind_formatted = self.kind.map_or_else(
243222
|| {
244-
snippet
245-
.default_kind_label
246-
.map(str::to_owned)
247-
.unwrap_or_default()
223+
if self.type_.is_function_type() {
224+
"(function) ".to_owned()
225+
} else {
226+
String::new()
227+
}
248228
},
249229
|kind| format!("{} ", kind.display_for_hover()),
250230
);
251231
let name_formatted = self
252232
.name
253233
.as_ref()
254234
.map_or("".to_owned(), |s| format!("{s}: "));
235+
let symbol_def_formatted = if self.show_go_to_links {
236+
HoverValue::format_symbol_def_locations(&self.type_).unwrap_or("".to_owned())
237+
} else {
238+
String::new()
239+
};
240+
let type_display = self.display.clone().unwrap_or_else(|| {
241+
self.type_
242+
.as_hover_string_with_fallback_name(self.name.as_deref())
243+
});
255244

256245
Hover {
257246
contents: HoverContents::Markup(MarkupContent {
@@ -450,15 +439,6 @@ pub fn get_hover(
450439
// Otherwise, fall through to the existing type hover logic
451440
let type_ = transaction.get_type_at(handle, position)?;
452441
let fallback_name_from_type = fallback_hover_name_from_type(&type_);
453-
let type_display = transaction.ad_hoc_solve(handle, {
454-
let mut cloned = type_.clone();
455-
move |solver| {
456-
// If the type is a callable, rewrite the signature to expand TypedDict-based
457-
// `**kwargs` entries, ensuring hover text shows the actual keyword names users can pass.
458-
cloned.visit_toplevel_callable_mut(|c| expand_callable_kwargs_for_hover(&solver, c));
459-
cloned.as_hover_string()
460-
}
461-
});
462442
let (kind, name, docstring_range, module) = if let Some(FindDefinitionItemWithDocstring {
463443
metadata,
464444
definition_range: definition_location,
@@ -499,6 +479,15 @@ pub fn get_hover(
499479

500480
let name = name.or_else(|| identifier_text_at(transaction, handle, position));
501481

482+
let name_for_display = name.clone();
483+
let type_display = transaction.ad_hoc_solve(handle, {
484+
let mut cloned = type_.clone();
485+
move |solver| {
486+
cloned.visit_toplevel_callable_mut(|c| expand_callable_kwargs_for_hover(&solver, c));
487+
cloned.as_hover_string_with_fallback_name(name_for_display.as_deref())
488+
}
489+
});
490+
502491
let docstring = if let (Some(docstring), Some(module)) = (docstring_range, module) {
503492
Some(Docstring(docstring, module))
504493
} else {

pyrefly/lib/test/lsp/lsp_interaction/hover.rs

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -95,28 +95,24 @@ fn hover_shows_third_party_function_name() {
9595
let root = get_test_files_root();
9696
let mut interaction = LspInteraction::new();
9797
interaction.set_root(root.path().join("rename_third_party"));
98-
interaction.initialize(InitializeSettings {
99-
configuration: Some(None),
100-
..Default::default()
101-
});
98+
interaction
99+
.initialize(InitializeSettings {
100+
configuration: Some(None),
101+
..Default::default()
102+
})
103+
.unwrap();
102104

103105
interaction.client.did_open("user_code.py");
104106
// Column/line values follow LSP's zero-based positions
105-
interaction.client.hover("user_code.py", 14, 25);
106-
interaction.client.expect_response_with(
107-
|response| {
108-
response
109-
.result
110-
.as_ref()
111-
.and_then(|value| value.get("contents"))
112-
.and_then(|contents| contents.get("value"))
113-
.and_then(|value| value.as_str())
114-
.is_some_and(|value| value.contains("external_function"))
115-
},
116-
"hover should show the function name for third-party modules",
117-
);
118-
119-
interaction.shutdown();
107+
interaction
108+
.client
109+
.hover("user_code.py", 14, 25)
110+
.expect_hover_response_with_markup(|value| {
111+
value.is_some_and(|text| text.contains("external_function"))
112+
})
113+
.unwrap();
114+
115+
interaction.shutdown().unwrap();
120116
}
121117

122118
#[test]

0 commit comments

Comments
 (0)