Skip to content

Commit 0f0f773

Browse files
SamChou19815facebook-github-bot
authored andcommitted
Support signature help
Summary: This diff implements signature help. We can generate signature help by inspecting the type of the callee when it can be converted into a `Type::Callable`. In addition, for overloads, we will use the information recorded in the last diff to show the signature help of the likely chosen overload, as well as all other options. Reviewed By: kinto0 Differential Revision: D76222222 fbshipit-source-id: 51569c32fb9450faa3721d3d89d1024da4f2be7a
1 parent 0efa12f commit 0f0f773

File tree

7 files changed

+478
-1
lines changed

7 files changed

+478
-1
lines changed

pyrefly/lib/alt/answers.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,6 @@ impl Answers {
557557
}
558558

559559
/// Returns all the overload, and the index of a chosen one
560-
#[allow(dead_code)]
561560
pub fn get_all_overload_trace(
562561
&self,
563562
range: TextRange,

pyrefly/lib/commands/lsp.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ use lsp_types::RegistrationParams;
8787
use lsp_types::RelatedFullDocumentDiagnosticReport;
8888
use lsp_types::RelativePattern;
8989
use lsp_types::ServerCapabilities;
90+
use lsp_types::SignatureHelp;
91+
use lsp_types::SignatureHelpOptions;
92+
use lsp_types::SignatureHelpParams;
9093
use lsp_types::TextDocumentSyncCapability;
9194
use lsp_types::TextDocumentSyncKind;
9295
use lsp_types::TextEdit;
@@ -119,6 +122,7 @@ use lsp_types::request::HoverRequest;
119122
use lsp_types::request::InlayHintRequest;
120123
use lsp_types::request::References;
121124
use lsp_types::request::RegisterCapability;
125+
use lsp_types::request::SignatureHelpRequest;
122126
use lsp_types::request::UnregisterCapability;
123127
use lsp_types::request::WorkspaceConfiguration;
124128
use path_absolutize::Absolutize;
@@ -572,6 +576,10 @@ pub fn run_lsp(
572576
Some(OneOf::Left(true))
573577
}
574578
},
579+
signature_help_provider: Some(SignatureHelpOptions {
580+
trigger_characters: Some(vec!["(".to_owned(), ",".to_owned()]),
581+
..Default::default()
582+
}),
575583
hover_provider: Some(HoverProviderCapability::Simple(true)),
576584
inlay_hint_provider: Some(OneOf::Left(true)),
577585
document_symbol_provider: Some(OneOf::Left(true)),
@@ -813,6 +821,14 @@ impl Server {
813821
ide_transaction_manager.save(transaction);
814822
} else if let Some(params) = as_request::<References>(&x) {
815823
self.references(x.id, ide_transaction_manager, params);
824+
} else if let Some(params) = as_request::<SignatureHelpRequest>(&x) {
825+
let transaction =
826+
ide_transaction_manager.non_commitable_transaction(&self.state);
827+
self.send_response(new_response(
828+
x.id,
829+
Ok(self.signature_help(&transaction, params)),
830+
));
831+
ide_transaction_manager.save(transaction);
816832
} else if let Some(params) = as_request::<HoverRequest>(&x) {
817833
let default_response = Hover {
818834
contents: HoverContents::Array(Vec::new()),
@@ -1491,6 +1507,18 @@ impl Server {
14911507
});
14921508
}
14931509

1510+
fn signature_help(
1511+
&self,
1512+
transaction: &Transaction<'_>,
1513+
params: SignatureHelpParams,
1514+
) -> Option<SignatureHelp> {
1515+
let uri = &params.text_document_position_params.text_document.uri;
1516+
let handle = self.make_handle_if_enabled(uri)?;
1517+
let info = transaction.get_module_info(&handle)?;
1518+
let position = position_to_text_size(&info, params.text_document_position_params.position);
1519+
transaction.get_signature_help_at(&handle, position)
1520+
}
1521+
14941522
fn hover(&self, transaction: &Transaction<'_>, params: HoverParams) -> Option<Hover> {
14951523
let uri = &params.text_document_position_params.text_document.uri;
14961524
let handle = self.make_handle_if_enabled(uri)?;

pyrefly/lib/state/lsp.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ use itertools::Itertools;
1212
use lsp_types::CompletionItem;
1313
use lsp_types::CompletionItemKind;
1414
use lsp_types::DocumentSymbol;
15+
use lsp_types::ParameterInformation;
16+
use lsp_types::ParameterLabel;
17+
use lsp_types::SignatureHelp;
18+
use lsp_types::SignatureInformation;
1519
use pyrefly_util::gas::Gas;
20+
use pyrefly_util::prelude::SliceExt;
1621
use pyrefly_util::prelude::VecExt;
1722
use pyrefly_util::task_heap::Cancelled;
1823
use pyrefly_util::visit::Visit;
@@ -55,8 +60,11 @@ use crate::state::require::Require;
5560
use crate::state::state::CancellableTransaction;
5661
use crate::state::state::Transaction;
5762
use crate::sys_info::SysInfo;
63+
use crate::types::callable::Param;
64+
use crate::types::callable::Params;
5865
use crate::types::lsp::source_range_to_range;
5966
use crate::types::module::Module;
67+
use crate::types::types::BoundMethodType;
6068
use crate::types::types::Type;
6169

6270
const INITIAL_GAS: Gas = Gas::new(20);
@@ -321,6 +329,120 @@ impl<'a> Transaction<'a> {
321329
}
322330
}
323331

332+
pub fn get_signature_help_at(
333+
&self,
334+
handle: &Handle,
335+
position: TextSize,
336+
) -> Option<SignatureHelp> {
337+
let mod_module = self.get_ast(handle)?;
338+
fn visit(x: &Expr, find: TextSize, res: &mut Option<(TextRange, TextRange, usize)>) {
339+
if let Expr::Call(call) = x
340+
&& call.arguments.range.contains_inclusive(find)
341+
{
342+
for (i, arg) in call.arguments.args.as_ref().iter().enumerate() {
343+
if arg.range().contains_inclusive(find) {
344+
visit(arg, find, res);
345+
if res.is_some() {
346+
return;
347+
}
348+
*res = Some((call.func.range(), call.arguments.range, i));
349+
}
350+
}
351+
if res.is_none() {
352+
*res = Some((
353+
call.func.range(),
354+
call.arguments.range,
355+
call.arguments.len(),
356+
));
357+
}
358+
} else {
359+
x.recurse(&mut |x| visit(x, find, res));
360+
}
361+
}
362+
let mut res = None;
363+
mod_module.visit(&mut |x| visit(x, position, &mut res));
364+
let (callee_range, call_args_range, arg_index) = res?;
365+
let answers = self.get_answers(handle)?;
366+
if let Some((overloads, chosen_overload_index)) =
367+
answers.get_all_overload_trace(call_args_range)
368+
{
369+
let signatures = overloads.into_map(|callable| {
370+
Self::create_signature_information(Type::Callable(Box::new(callable)), arg_index)
371+
});
372+
Some(SignatureHelp {
373+
signatures,
374+
active_signature: chosen_overload_index.map(|i| i as u32),
375+
active_parameter: Some(arg_index as u32),
376+
})
377+
} else {
378+
answers
379+
.get_type_trace(callee_range)
380+
.map(|callee_type| SignatureHelp {
381+
signatures: vec![Self::create_signature_information(
382+
callee_type.arc_clone(),
383+
arg_index,
384+
)],
385+
active_signature: Some(0),
386+
active_parameter: Some(arg_index as u32),
387+
})
388+
}
389+
}
390+
391+
fn create_signature_information(type_: Type, arg_index: usize) -> SignatureInformation {
392+
let type_ = type_.deterministic_printing();
393+
let label = format!("{}", type_);
394+
let (parameters, active_parameter) = if let Some(params) =
395+
Self::normalize_singleton_function_type_for_signature_help(type_)
396+
{
397+
let active_parameter = if arg_index < params.len() {
398+
Some(arg_index as u32)
399+
} else {
400+
None
401+
};
402+
(
403+
Some(params.map(|param| ParameterInformation {
404+
label: ParameterLabel::Simple(format!("{}", param)),
405+
documentation: None,
406+
})),
407+
active_parameter,
408+
)
409+
} else {
410+
(None, None)
411+
};
412+
SignatureInformation {
413+
label,
414+
documentation: None,
415+
parameters,
416+
active_parameter,
417+
}
418+
}
419+
420+
fn normalize_singleton_function_type_for_signature_help(type_: Type) -> Option<Vec<Param>> {
421+
let callable = match type_ {
422+
Type::Callable(callable) => Some(*callable),
423+
Type::Function(function) => Some(function.signature),
424+
Type::BoundMethod(bound_method) => match bound_method.func {
425+
BoundMethodType::Function(function) => Some(function.signature),
426+
BoundMethodType::Forall(forall) => Some(forall.body.signature),
427+
BoundMethodType::Overload(_) => None,
428+
},
429+
_ => None,
430+
}?;
431+
// We will drop the self parameter for signature help
432+
if let Params::List(params_list) = callable.params {
433+
if let Some(Param::PosOnly(Some(name), _, _) | Param::Pos(name, _, _)) =
434+
params_list.items().first()
435+
&& name.as_str() == "self"
436+
{
437+
let mut params = params_list.into_items();
438+
params.remove(0);
439+
return Some(params);
440+
}
441+
return Some(params_list.into_items());
442+
}
443+
None
444+
}
445+
324446
fn resolve_named_import(
325447
&self,
326448
handle: &Handle,

pyrefly/lib/test/lsp/lsp_interaction_util.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ use lsp_types::CompletionOptions;
2626
use lsp_types::HoverProviderCapability;
2727
use lsp_types::OneOf;
2828
use lsp_types::ServerCapabilities;
29+
use lsp_types::SignatureHelpOptions;
2930
use lsp_types::TextDocumentSyncCapability;
3031
use lsp_types::TextDocumentSyncKind;
3132
use lsp_types::Url;
@@ -300,6 +301,10 @@ fn get_initialize_responses(find_refs: bool) -> Vec<Message> {
300301
} else {
301302
None
302303
},
304+
signature_help_provider: Some(SignatureHelpOptions {
305+
trigger_characters: Some(vec!["(".to_owned(), ",".to_owned()]),
306+
..Default::default()
307+
}),
303308
hover_provider: Some(HoverProviderCapability::Simple(true)),
304309
inlay_hint_provider: Some(OneOf::Left(true)),
305310
document_symbol_provider: Some(OneOf::Left(true)),

pyrefly/lib/test/lsp/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ mod inlay_hint;
1717
mod local_find_refs;
1818
mod lsp_interaction;
1919
mod lsp_interaction_util;
20+
mod signature_help;

0 commit comments

Comments
 (0)