@@ -21,6 +21,7 @@ use pyrefly_python::docstring::parse_parameter_documentation;
2121use pyrefly_python:: ignore:: Ignore ;
2222use pyrefly_python:: ignore:: Tool ;
2323use pyrefly_python:: ignore:: find_comment_start_in_line;
24+ use pyrefly_python:: module:: Module ;
2425use pyrefly_python:: symbol_kind:: SymbolKind ;
2526use pyrefly_types:: callable:: Callable ;
2627use 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.
436461fn 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) {
0 commit comments