@@ -15,15 +15,23 @@ use pyrefly_python::docstring::Docstring;
1515use pyrefly_python:: ignore:: Ignore ;
1616use pyrefly_python:: ignore:: find_comment_start_in_line;
1717use pyrefly_python:: symbol_kind:: SymbolKind ;
18+ use pyrefly_types:: callable:: Callable ;
19+ use pyrefly_types:: callable:: Param ;
20+ use pyrefly_types:: callable:: ParamList ;
21+ use pyrefly_types:: callable:: Params ;
22+ use pyrefly_types:: callable:: Required ;
1823use pyrefly_types:: types:: Type ;
1924use pyrefly_util:: lined_buffer:: LineNumber ;
25+ use ruff_python_ast:: name:: Name ;
2026use ruff_text_size:: TextSize ;
2127
28+ use crate :: alt:: answers_solver:: AnswersSolver ;
2229use crate :: error:: error:: Error ;
2330use crate :: lsp:: module_helpers:: collect_symbol_def_paths;
2431use crate :: state:: lsp:: FindDefinitionItemWithDocstring ;
2532use crate :: state:: lsp:: FindPreference ;
2633use crate :: state:: state:: Transaction ;
34+ use crate :: state:: state:: TransactionHandle ;
2735
2836/// Gets all suppressed errors that overlap with the given line.
2937///
@@ -95,6 +103,7 @@ pub struct HoverValue {
95103 pub name : Option < String > ,
96104 pub type_ : Type ,
97105 pub docstring : Option < Docstring > ,
106+ pub display : Option < String > ,
98107}
99108
100109impl HoverValue {
@@ -158,6 +167,10 @@ impl HoverValue {
158167 . map_or ( "" . to_owned ( ) , |s| format ! ( "{s}: " ) ) ;
159168 let symbol_def_formatted =
160169 HoverValue :: format_symbol_def_locations ( & self . type_ ) . unwrap_or ( "" . to_owned ( ) ) ;
170+ let type_display = self
171+ . display
172+ . clone ( )
173+ . unwrap_or_else ( || self . type_ . as_hover_string ( ) ) ;
161174
162175 Hover {
163176 contents : HoverContents :: Markup ( MarkupContent {
@@ -166,7 +179,7 @@ impl HoverValue {
166179 "```python\n {}{}{}\n ```{}{}" ,
167180 kind_formatted,
168181 name_formatted,
169- self . type_ . as_hover_string ( ) ,
182+ type_display ,
170183 docstring_formatted,
171184 symbol_def_formatted
172185 ) ,
@@ -176,6 +189,49 @@ impl HoverValue {
176189 }
177190}
178191
192+ fn collect_typed_dict_fields_for_hover < ' a > (
193+ solver : & AnswersSolver < TransactionHandle < ' a > > ,
194+ ty : & Type ,
195+ ) -> Option < Vec < ( Name , Type , Required ) > > {
196+ match ty {
197+ Type :: Unpack ( inner) => match inner. as_ref ( ) {
198+ Type :: TypedDict ( typed_dict) => {
199+ let fields = solver. type_order ( ) . typed_dict_kw_param_info ( typed_dict) ;
200+ if fields. is_empty ( ) {
201+ None
202+ } else {
203+ Some ( fields)
204+ }
205+ }
206+ _ => None ,
207+ } ,
208+ _ => None ,
209+ }
210+ }
211+
212+ fn expand_callable_kwargs_for_hover < ' a > (
213+ solver : & AnswersSolver < TransactionHandle < ' a > > ,
214+ callable : & mut Callable ,
215+ ) {
216+ if let Params :: List ( param_list) = & mut callable. params {
217+ let mut expanded = Vec :: with_capacity ( param_list. len ( ) ) ;
218+ let mut changed = false ;
219+ for param in param_list. items ( ) {
220+ if let Param :: Kwargs ( _, ty) = param
221+ && let Some ( fields) = collect_typed_dict_fields_for_hover ( solver, ty)
222+ {
223+ changed = true ;
224+ for ( field_name, field_type, required) in fields {
225+ expanded. push ( Param :: KwOnly ( field_name, field_type, required) ) ;
226+ }
227+ }
228+ expanded. push ( param. clone ( ) ) ;
229+ }
230+ if changed {
231+ * param_list = ParamList :: new ( expanded) ;
232+ }
233+ }
234+ }
179235pub fn get_hover (
180236 transaction : & Transaction < ' _ > ,
181237 handle : & Handle ,
@@ -213,6 +269,15 @@ pub fn get_hover(
213269
214270 // Otherwise, fall through to the existing type hover logic
215271 let type_ = transaction. get_type_at ( handle, position) ?;
272+ let type_display = transaction. ad_hoc_solve ( handle, {
273+ let mut cloned = type_. clone ( ) ;
274+ move |solver| {
275+ // If the type is a callable, rewrite the signature to expand TypedDict-based
276+ // `**kwargs` entries, ensuring hover text shows the actual keyword names users can pass.
277+ cloned. visit_toplevel_callable_mut ( |c| expand_callable_kwargs_for_hover ( & solver, c) ) ;
278+ cloned. as_hover_string ( )
279+ }
280+ } ) ;
216281 let ( kind, name, docstring_range, module) = if let Some ( FindDefinitionItemWithDocstring {
217282 metadata,
218283 definition_range : definition_location,
@@ -253,6 +318,7 @@ pub fn get_hover(
253318 name,
254319 type_,
255320 docstring,
321+ display : type_display,
256322 }
257323 . format ( ) ,
258324 )
0 commit comments