Refactor CallInfo function signatures to new FunctionSignature type

This is used by CallInfo to create a pretty printed function signature that can
be used with completions and other places as well.
This commit is contained in:
Ville Penttinen 2019-03-12 09:24:46 +02:00
parent 5f700179fc
commit 0e49abb7fb
8 changed files with 212 additions and 71 deletions

View file

@ -22,6 +22,12 @@ impl Into<String> for Documentation {
} }
} }
impl<'a> Into<String> for &'a Documentation {
fn into(self) -> String {
self.contents().into()
}
}
pub trait Docs { pub trait Docs {
fn docs(&self, db: &impl HirDatabase) -> Option<Documentation>; fn docs(&self, db: &impl HirDatabase) -> Option<Documentation>;
} }

View file

@ -30,7 +30,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
let mut call_info = CallInfo::new(db, function, fn_def)?; let mut call_info = CallInfo::new(db, function, fn_def)?;
// If we have a calling expression let's find which argument we are on // If we have a calling expression let's find which argument we are on
let num_params = call_info.parameters.len(); let num_params = call_info.parameters().len();
let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some();
if num_params == 1 { if num_params == 1 {
@ -108,27 +108,26 @@ impl<'a> FnCallNode<'a> {
impl CallInfo { impl CallInfo {
fn new(db: &RootDatabase, function: hir::Function, node: &ast::FnDef) -> Option<Self> { fn new(db: &RootDatabase, function: hir::Function, node: &ast::FnDef) -> Option<Self> {
let label = crate::completion::function_label(node)?; let sig = crate::completion::function_signature(node)?;
let doc = function.docs(db); let doc = function.docs(db);
let sig = sig.with_doc_opt(doc);
Some(CallInfo { parameters: param_list(node), label, doc, active_parameter: None }) Some(CallInfo { signature: sig, active_parameter: None })
}
}
fn param_list(node: &ast::FnDef) -> Vec<String> {
let mut res = vec![];
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
res.push(self_param.syntax().text().to_string())
} }
// Maybe use param.pat here? See if we can just extract the name? fn parameters(&self) -> &[String] {
//res.extend(param_list.params().map(|p| p.syntax().text().to_string())); &self.signature.parameters
res.extend( }
param_list.params().filter_map(|p| p.pat()).map(|pat| pat.syntax().text().to_string()),
); #[cfg(test)]
fn doc(&self) -> Option<&hir::Documentation> {
self.signature.doc.as_ref()
}
#[cfg(test)]
fn label(&self) -> String {
self.signature.to_string()
} }
res
} }
#[cfg(test)] #[cfg(test)]
@ -151,7 +150,7 @@ mod tests {
fn bar() { foo(<|>3, ); }"#, fn bar() { foo(<|>3, ); }"#,
); );
assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string())); assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
} }
@ -162,7 +161,7 @@ fn bar() { foo(<|>3, ); }"#,
fn bar() { foo(3, <|>); }"#, fn bar() { foo(3, <|>); }"#,
); );
assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string())); assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
assert_eq!(info.active_parameter, Some(1)); assert_eq!(info.active_parameter, Some(1));
} }
@ -173,7 +172,27 @@ fn bar() { foo(3, <|>); }"#,
fn bar() { foo(<|>); }"#, fn bar() { foo(<|>); }"#,
); );
assert_eq!(info.parameters, vec!("x".to_string(), "y".to_string())); assert_eq!(info.parameters(), ["x: u32", "y: u32"]);
assert_eq!(info.active_parameter, Some(0));
}
#[test]
fn test_fn_signature_two_args_first_generics() {
let info = call_info(
r#"fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 where T: Copy + Display, U: Debug {x + y}
fn bar() { foo(<|>3, ); }"#,
);
assert_eq!(info.parameters(), ["x: T", "y: U"]);
assert_eq!(
info.label(),
r#"
fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
where T: Copy + Display,
U: Debug
"#
.trim()
);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
} }
@ -184,7 +203,7 @@ fn bar() { foo(<|>); }"#,
fn bar() {let _ : F = F::new(<|>);}"#, fn bar() {let _ : F = F::new(<|>);}"#,
); );
assert_eq!(info.parameters, Vec::<String>::new()); assert!(info.parameters().is_empty());
assert_eq!(info.active_parameter, None); assert_eq!(info.active_parameter, None);
} }
@ -206,7 +225,7 @@ fn bar() {
}"#, }"#,
); );
assert_eq!(info.parameters, vec!["&self".to_string()]); assert_eq!(info.parameters(), ["&self"]);
assert_eq!(info.active_parameter, None); assert_eq!(info.active_parameter, None);
} }
@ -228,7 +247,7 @@ fn bar() {
}"#, }"#,
); );
assert_eq!(info.parameters, vec!["&self".to_string(), "x".to_string()]); assert_eq!(info.parameters(), ["&self", "x: i32"]);
assert_eq!(info.active_parameter, Some(1)); assert_eq!(info.active_parameter, Some(1));
} }
@ -248,10 +267,10 @@ fn bar() {
"#, "#,
); );
assert_eq!(info.parameters, vec!["j".to_string()]); assert_eq!(info.parameters(), ["j: u32"]);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
assert_eq!(info.label, "fn foo(j: u32) -> u32".to_string()); assert_eq!(info.label(), "fn foo(j: u32) -> u32");
assert_eq!(info.doc.map(|it| it.into()), Some("test".to_string())); assert_eq!(info.doc().map(|it| it.into()), Some("test".to_string()));
} }
#[test] #[test]
@ -276,11 +295,11 @@ pub fn do() {
}"#, }"#,
); );
assert_eq!(info.parameters, vec!["x".to_string()]); assert_eq!(info.parameters(), ["x: i32"]);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32");
assert_eq!( assert_eq!(
info.doc.map(|it| it.into()), info.doc().map(|it| it.into()),
Some( Some(
r#"Adds one to the number given. r#"Adds one to the number given.
@ -322,11 +341,11 @@ pub fn do_it() {
}"#, }"#,
); );
assert_eq!(info.parameters, vec!["x".to_string()]); assert_eq!(info.parameters(), ["x: i32"]);
assert_eq!(info.active_parameter, Some(0)); assert_eq!(info.active_parameter, Some(0));
assert_eq!(info.label, "pub fn add_one(x: i32) -> i32".to_string()); assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32");
assert_eq!( assert_eq!(
info.doc.map(|it| it.into()), info.doc().map(|it| it.into()),
Some( Some(
r#"Adds one to the number given. r#"Adds one to the number given.
@ -375,10 +394,10 @@ pub fn foo() {
"#, "#,
); );
assert_eq!(info.parameters, vec!["&mut self".to_string(), "ctx".to_string()]); assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]);
assert_eq!(info.active_parameter, Some(1)); assert_eq!(info.active_parameter, Some(1));
assert_eq!( assert_eq!(
info.doc.map(|it| it.into()), info.doc().map(|it| it.into()),
Some( Some(
r#"Method is called when writer finishes. r#"Method is called when writer finishes.

View file

@ -13,11 +13,12 @@ mod complete_scope;
mod complete_postfix; mod complete_postfix;
use ra_db::SourceDatabase; use ra_db::SourceDatabase;
use ra_syntax::{ast::{self, AstNode}, SyntaxKind::{ATTR, COMMENT}}; use ra_syntax::{ast::{self, AstNode, NameOwner, VisibilityOwner, TypeParamsOwner}, SyntaxKind::{ATTR, COMMENT}};
use crate::{ use crate::{
db, db,
FilePosition, FilePosition,
FunctionSignature,
completion::{ completion::{
completion_item::{Completions, CompletionKind}, completion_item::{Completions, CompletionKind},
completion_context::CompletionContext, completion_context::CompletionContext,
@ -71,22 +72,52 @@ pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Opti
Some(acc) Some(acc)
} }
pub fn function_label(node: &ast::FnDef) -> Option<String> { pub fn generic_parameters<N: TypeParamsOwner>(node: &N) -> Vec<String> {
let label: String = if let Some(body) = node.body() { let mut res = vec![];
let body_range = body.syntax().range(); if let Some(type_params) = node.type_param_list() {
let label: String = node res.extend(type_params.lifetime_params().map(|p| p.syntax().text().to_string()));
.syntax() res.extend(type_params.type_params().map(|p| p.syntax().text().to_string()));
.children_with_tokens() }
.filter(|child| !child.range().is_subrange(&body_range)) // Filter out body res
.filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) // Filter out comments and attrs }
.map(|node| node.to_string())
.collect(); pub fn where_predicates<N: TypeParamsOwner>(node: &N) -> Vec<String> {
label let mut res = vec![];
} else { if let Some(clause) = node.where_clause() {
node.syntax().text().to_string() res.extend(clause.predicates().map(|p| p.syntax().text().to_string()));
}
res
}
pub fn function_signature(node: &ast::FnDef) -> Option<FunctionSignature> {
fn param_list(node: &ast::FnDef) -> Vec<String> {
let mut res = vec![];
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
res.push(self_param.syntax().text().to_string())
}
res.extend(param_list.params().map(|param| param.syntax().text().to_string()));
}
res
}
let sig = FunctionSignature {
visibility: node.visibility().map(|n| n.syntax().text().to_string()),
name: node.name().map(|n| n.text().to_string()),
ret_type: node.ret_type().and_then(|r| r.type_ref()).map(|n| n.syntax().text().to_string()),
parameters: param_list(node),
generic_parameters: generic_parameters(node),
where_predicates: where_predicates(node),
// docs are processed separately
doc: None,
}; };
Some(label.trim().to_owned()) Some(sig)
}
pub fn function_label(node: &ast::FnDef) -> Option<String> {
function_signature(node).map(|n| n.to_string())
} }
pub fn const_label(node: &ast::ConstDef) -> String { pub fn const_label(node: &ast::ConstDef) -> String {

View file

@ -145,7 +145,7 @@ mod tests {
check_reference_completion( check_reference_completion(
"dont_show_both_completions_for_shadowing", "dont_show_both_completions_for_shadowing",
r" r"
fn foo() -> { fn foo() {
let bar = 92; let bar = 92;
{ {
let bar = 62; let bar = 62;

View file

@ -0,0 +1,51 @@
use super::*;
use std::fmt::{self, Display};
impl Display for FunctionSignature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if let Some(t) = &self.visibility {
write!(f, "{} ", t)?;
}
if let Some(name) = &self.name {
write!(f, "fn {}", name)?;
}
if !self.generic_parameters.is_empty() {
write!(f, "<")?;
write_joined(f, &self.generic_parameters, ", ")?;
write!(f, ">")?;
}
write!(f, "(")?;
write_joined(f, &self.parameters, ", ")?;
write!(f, ")")?;
if let Some(t) = &self.ret_type {
write!(f, " -> {}", t)?;
}
if !self.where_predicates.is_empty() {
write!(f, "\nwhere ")?;
write_joined(f, &self.where_predicates, ",\n ")?;
}
Ok(())
}
}
fn write_joined<T: Display>(
f: &mut fmt::Formatter,
items: impl IntoIterator<Item = T>,
sep: &str,
) -> fmt::Result {
let mut first = true;
for e in items {
if !first {
write!(f, "{}", sep)?;
}
first = false;
write!(f, "{}", e)?;
}
Ok(())
}

View file

@ -37,6 +37,7 @@ mod join_lines;
mod structure; mod structure;
mod typing; mod typing;
mod matching_brace; mod matching_brace;
mod display;
#[cfg(test)] #[cfg(test)]
mod marks; mod marks;
@ -243,12 +244,36 @@ impl<T> RangeInfo<T> {
#[derive(Debug)] #[derive(Debug)]
pub struct CallInfo { pub struct CallInfo {
pub label: String, pub signature: FunctionSignature,
pub doc: Option<Documentation>,
pub parameters: Vec<String>,
pub active_parameter: Option<usize>, pub active_parameter: Option<usize>,
} }
/// Contains information about a function signature
#[derive(Debug)]
pub struct FunctionSignature {
/// Optional visibility
pub visibility: Option<String>,
/// Name of the function
pub name: Option<String>,
/// Documentation for the function
pub doc: Option<Documentation>,
/// Generic parameters
pub generic_parameters: Vec<String>,
/// Parameters of the function
pub parameters: Vec<String>,
/// Optional return type
pub ret_type: Option<String>,
/// Where predicates
pub where_predicates: Vec<String>,
}
impl FunctionSignature {
pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self {
self.doc = doc;
self
}
}
/// `AnalysisHost` stores the current state of the world. /// `AnalysisHost` stores the current state of the world.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct AnalysisHost { pub struct AnalysisHost {

View file

@ -174,6 +174,28 @@ impl Conv for ra_ide_api::Documentation {
} }
} }
impl Conv for ra_ide_api::FunctionSignature {
type Output = lsp_types::SignatureInformation;
fn conv(self) -> Self::Output {
use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation};
let label = self.to_string();
let documentation = self.doc.map(|it| it.conv());
let parameters: Vec<ParameterInformation> = self
.parameters
.into_iter()
.map(|param| ParameterInformation {
label: ParameterLabel::Simple(param),
documentation: None,
})
.collect();
SignatureInformation { label, documentation, parameters: Some(parameters) }
}
}
impl ConvWith for TextEdit { impl ConvWith for TextEdit {
type Ctx = LineIndex; type Ctx = LineIndex;
type Output = Vec<lsp_types::TextEdit>; type Output = Vec<lsp_types::TextEdit>;

View file

@ -3,8 +3,8 @@ use lsp_types::{
CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, CodeAction, CodeActionResponse, CodeLens, Command, Diagnostic, DiagnosticSeverity, CodeAction,
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange,
FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent, FoldingRangeKind, FoldingRangeParams, Hover, HoverContents, Location, MarkupContent,
MarkupKind, ParameterInformation, ParameterLabel, Position, PrepareRenameResponse, Range, MarkupKind, Position, PrepareRenameResponse, Range,
RenameParams, SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, RenameParams,SymbolInformation, TextDocumentIdentifier, TextEdit,
WorkspaceEdit, WorkspaceEdit,
}; };
use ra_ide_api::{ use ra_ide_api::{
@ -403,26 +403,13 @@ pub fn handle_signature_help(
) -> Result<Option<req::SignatureHelp>> { ) -> Result<Option<req::SignatureHelp>> {
let position = params.try_conv_with(&world)?; let position = params.try_conv_with(&world)?;
if let Some(call_info) = world.analysis().call_info(position)? { if let Some(call_info) = world.analysis().call_info(position)? {
let parameters: Vec<ParameterInformation> = call_info let active_parameter = call_info.active_parameter.map(|it| it as i64);
.parameters let sig_info = call_info.signature.conv();
.into_iter()
.map(|param| ParameterInformation {
label: ParameterLabel::Simple(param.clone()),
documentation: None,
})
.collect();
let documentation = call_info.doc.map(|it| it.conv());
let sig_info = SignatureInformation {
label: call_info.label,
documentation,
parameters: Some(parameters),
};
Ok(Some(req::SignatureHelp { Ok(Some(req::SignatureHelp {
signatures: vec![sig_info], signatures: vec![sig_info],
active_signature: Some(0), active_signature: Some(0),
active_parameter: call_info.active_parameter.map(|it| it as i64), active_parameter,
})) }))
} else { } else {
Ok(None) Ok(None)