Skip to content

Commit eee8f6d

Browse files
fix
1 parent ec4e852 commit eee8f6d

2 files changed

Lines changed: 143 additions & 66 deletions

File tree

pyrefly/lib/lsp/wasm/hover.rs

Lines changed: 111 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use pyrefly_python::docstring::parse_parameter_documentation;
2121
use pyrefly_python::ignore::Ignore;
2222
use pyrefly_python::ignore::Tool;
2323
use pyrefly_python::ignore::find_comment_start_in_line;
24+
use pyrefly_python::module::Module;
2425
use pyrefly_python::symbol_kind::SymbolKind;
2526
use pyrefly_types::callable::Callable;
2627
use pyrefly_types::callable::FunctionKind;
@@ -431,6 +432,30 @@ fn parameter_definition_documentation(
431432
docs.get(key).cloned().map(|doc| (key.to_owned(), doc))
432433
}
433434

435+
fn declared_function_hover_display(module: &Module, definition_range: TextRange) -> Option<String> {
436+
let (ast, _, _) = Ast::parse(module.contents(), module.source_type());
437+
let function_def = Ast::locate_node(&ast, definition_range.start())
438+
.into_iter()
439+
.find_map(|node| match node {
440+
AnyNodeRef::StmtFunctionDef(function_def)
441+
if function_def.name.range() == definition_range =>
442+
{
443+
Some(function_def)
444+
}
445+
_ => None,
446+
})?;
447+
let body_start = function_def
448+
.body
449+
.first()
450+
.map(Ranged::range)
451+
.map(|range| range.start())
452+
.unwrap_or(function_def.range.end());
453+
let header = module
454+
.code_at(TextRange::new(function_def.range.start(), body_start))
455+
.trim_end();
456+
header.ends_with(':').then(|| format!("{header} ..."))
457+
}
458+
434459
/// Check if the cursor position is on the `in` keyword within a for loop or comprehension.
435460
/// Returns Some(iterable_range) if found, None otherwise.
436461
fn in_keyword_in_iteration_at(
@@ -513,7 +538,7 @@ pub fn get_hover(
513538
});
514539
}
515540

516-
// Otherwise, fall through to the existing type hover logic
541+
// Otherwise, fall through to the existing type hover logic.
517542
let mut type_ = transaction.get_type_at(handle, position)?;
518543

519544
// Helper function to check if we're hovering over a callee and get its range
@@ -549,42 +574,49 @@ pub fn get_hover(
549574
}
550575

551576
let fallback_name_from_type = fallback_hover_name_from_type(&type_);
552-
let (kind, name, docstring_range, module) = if let Some(FindDefinitionItemWithDocstring {
553-
metadata,
554-
definition_range: definition_location,
555-
module,
556-
docstring_range,
557-
display_name,
558-
}) = transaction
559-
.find_definition(
560-
handle,
561-
position,
562-
FindPreference {
563-
prefer_pyi: false,
564-
..Default::default()
565-
},
566-
)
567-
.map(Vec1::into_vec)
568-
.unwrap_or_default()
569-
// TODO: handle more than 1 definition
570-
.into_iter()
571-
.next()
572-
{
573-
let kind = metadata.symbol_kind();
574-
let name = {
575-
let snippet = module.code_at(definition_location);
576-
if snippet.chars().any(|c| !c.is_whitespace()) {
577-
Some(snippet.to_owned())
578-
} else if let Some(name) = display_name.clone() {
579-
Some(name)
580-
} else {
581-
fallback_name_from_type
582-
}
577+
let (kind, name, definition_range, docstring_range, module) =
578+
if let Some(FindDefinitionItemWithDocstring {
579+
metadata,
580+
definition_range: definition_location,
581+
module,
582+
docstring_range,
583+
display_name,
584+
}) = transaction
585+
.find_definition(
586+
handle,
587+
position,
588+
FindPreference {
589+
prefer_pyi: false,
590+
..Default::default()
591+
},
592+
)
593+
.map(Vec1::into_vec)
594+
.unwrap_or_default()
595+
// TODO: handle more than 1 definition
596+
.into_iter()
597+
.next()
598+
{
599+
let kind = metadata.symbol_kind();
600+
let name = {
601+
let snippet = module.code_at(definition_location);
602+
if snippet.chars().any(|c| !c.is_whitespace()) {
603+
Some(snippet.to_owned())
604+
} else if let Some(name) = display_name.clone() {
605+
Some(name)
606+
} else {
607+
fallback_name_from_type
608+
}
609+
};
610+
(
611+
kind,
612+
name,
613+
Some(definition_location),
614+
docstring_range,
615+
Some(module),
616+
)
617+
} else {
618+
(None, fallback_name_from_type, None, None, None)
583619
};
584-
(kind, name, docstring_range, Some(module))
585-
} else {
586-
(None, fallback_name_from_type, None, None)
587-
};
588620

589621
let name = name.or_else(|| identifier_text_at(transaction, handle, position));
590622

@@ -593,39 +625,52 @@ pub fn get_hover(
593625
&& !transaction
594626
.identifier_at(handle, position)
595627
.is_some_and(|id| matches!(id.context, IdentifierContext::ClassDef { .. }));
596-
let type_display = transaction.ad_hoc_solve(handle, "hover_display", {
597-
let mut cloned = type_.clone();
598-
move |solver| {
599-
if show_constructor {
600-
let constructor = match cloned {
601-
Type::ClassDef(ref cls)
602-
if !solver.get_metadata_for_class(cls).is_typed_dict() =>
603-
{
604-
Some(solver.type_order().constructor_to_callable(
605-
&solver.promote_nontypeddict_silently_to_classtype(cls),
606-
))
607-
}
608-
Type::Type(box Type::ClassType(ref cls)) => {
609-
Some(solver.type_order().constructor_to_callable(cls))
628+
let type_display = if callee_range_opt.is_none() {
629+
match (&type_, module.as_ref(), definition_range) {
630+
(Type::Function(_), Some(module), Some(definition_range)) => {
631+
declared_function_hover_display(module, definition_range)
632+
}
633+
_ => None,
634+
}
635+
} else {
636+
None
637+
}
638+
.or_else(|| {
639+
transaction.ad_hoc_solve(handle, "hover_display", {
640+
let mut cloned = type_.clone();
641+
move |solver| {
642+
if show_constructor {
643+
let constructor = match cloned {
644+
Type::ClassDef(ref cls)
645+
if !solver.get_metadata_for_class(cls).is_typed_dict() =>
646+
{
647+
Some(solver.type_order().constructor_to_callable(
648+
&solver.promote_nontypeddict_silently_to_classtype(cls),
649+
))
650+
}
651+
Type::Type(box Type::ClassType(ref cls)) => {
652+
Some(solver.type_order().constructor_to_callable(cls))
653+
}
654+
_ => None,
655+
};
656+
if let Some(mut constructor) = constructor {
657+
constructor.transform_toplevel_callable(|c| {
658+
expand_callable_kwargs_for_hover(&solver, c)
659+
});
660+
return constructor.as_lsp_string_with_fallback_name(
661+
name_for_display.as_deref(),
662+
LspDisplayMode::Hover,
663+
);
610664
}
611-
_ => None,
612-
};
613-
if let Some(mut constructor) = constructor {
614-
constructor.transform_toplevel_callable(|c| {
615-
expand_callable_kwargs_for_hover(&solver, c)
616-
});
617-
return constructor.as_lsp_string_with_fallback_name(
618-
name_for_display.as_deref(),
619-
LspDisplayMode::Hover,
620-
);
621665
}
666+
cloned
667+
.transform_toplevel_callable(|c| expand_callable_kwargs_for_hover(&solver, c));
668+
cloned.as_lsp_string_with_fallback_name(
669+
name_for_display.as_deref(),
670+
LspDisplayMode::Hover,
671+
)
622672
}
623-
cloned.transform_toplevel_callable(|c| expand_callable_kwargs_for_hover(&solver, c));
624-
cloned.as_lsp_string_with_fallback_name(
625-
name_for_display.as_deref(),
626-
LspDisplayMode::Hover,
627-
)
628-
}
673+
})
629674
});
630675

631676
let docstring = if let (Some(docstring), Some(module)) = (docstring_range, module) {

pyrefly/lib/test/lsp/hover.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,38 @@ greeter("hi")
177177
);
178178
}
179179

180+
#[test]
181+
fn hover_preserves_type_aliases_in_function_signatures() {
182+
let code = r#"
183+
from typing import TypeAlias
184+
185+
Messages: TypeAlias = list[str]
186+
187+
def func(msgs: Messages) -> None:
188+
pass
189+
190+
func
191+
#^
192+
"#;
193+
let report = get_batched_lsp_operations_report(&[("main", code)], |state, handle, position| {
194+
match get_hover(&state.transaction(), handle, position, false) {
195+
Some(Hover {
196+
contents: HoverContents::Markup(markup),
197+
..
198+
}) => markup.value,
199+
_ => "None".to_owned(),
200+
}
201+
});
202+
assert!(
203+
report.contains("def func(msgs: Messages) -> None: ..."),
204+
"Expected hover to preserve the alias name, got: {report}"
205+
);
206+
assert!(
207+
!report.contains("def func(msgs: list[str]) -> None: ..."),
208+
"Expected hover not to expand the alias, got: {report}"
209+
);
210+
}
211+
180212
#[test]
181213
fn hover_over_inline_ignore_comment() {
182214
let code = r#"

0 commit comments

Comments
 (0)