11791: fix: some fixes and improvements to signature help r=jonas-schievink a=jonas-schievink

bors r+

Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
This commit is contained in:
bors[bot] 2022-03-23 00:36:11 +00:00 committed by GitHub
commit 652233283b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 270 additions and 167 deletions

View File

@ -2,13 +2,14 @@
//! a call or use-site.
use either::Either;
use hir::{HasAttrs, HirDisplay, Semantics};
use ide_db::{
active_parameter::{callable_for_token, generics_for_token},
base_db::FilePosition,
};
use hir::{GenericParam, HasAttrs, HirDisplay, Semantics};
use ide_db::{active_parameter::callable_for_node, base_db::FilePosition};
use stdx::format_to;
use syntax::{algo, AstNode, Direction, TextRange, TextSize};
use syntax::{
algo,
ast::{self, HasArgList},
AstNode, Direction, SyntaxToken, TextRange, TextSize,
};
use crate::RootDatabase;
@ -65,25 +66,50 @@ pub(crate) fn signature_help(db: &RootDatabase, position: FilePosition) -> Optio
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
let token = sema.descend_into_macros_single(token);
if let Some((callable, active_parameter)) = callable_for_token(&sema, token.clone()) {
return Some(signature_help_for_callable(db, callable, active_parameter));
if let Some(help) = signature_help_for_call(&sema, &token) {
return Some(help);
}
if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) {
return signature_help_for_generics(db, generic_def, active_parameter);
if let Some(help) = signature_help_for_generics(&sema, &token) {
return Some(help);
}
None
}
fn signature_help_for_callable(
db: &RootDatabase,
callable: hir::Callable,
active_parameter: Option<usize>,
) -> SignatureHelp {
fn signature_help_for_call(
sema: &Semantics<RootDatabase>,
token: &SyntaxToken,
) -> Option<SignatureHelp> {
// Find the calling expression and its NameRef
let mut node = token.parent()?;
let calling_node = loop {
if let Some(callable) = ast::CallableExpr::cast(node.clone()) {
if callable
.arg_list()
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
{
break callable;
}
}
// Stop at multi-line expressions, since the signature of the outer call is not very
// helpful inside them.
if let Some(expr) = ast::Expr::cast(node.clone()) {
if expr.syntax().text().contains_char('\n') {
return None;
}
}
node = node.parent()?;
};
let (callable, active_parameter) = callable_for_node(sema, &calling_node, token)?;
let mut res =
SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter };
let db = sema.db;
match callable.kind() {
hir::CallableKind::Function(func) => {
res.doc = func.docs(db).map(|it| it.into());
@ -134,21 +160,73 @@ fn signature_help_for_callable(
}
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
}
res
Some(res)
}
fn signature_help_for_generics(
db: &RootDatabase,
mut generics_def: hir::GenericDef,
active_parameter: usize,
sema: &Semantics<RootDatabase>,
token: &SyntaxToken,
) -> Option<SignatureHelp> {
let parent = token.parent()?;
let arg_list = parent
.ancestors()
.filter_map(ast::GenericArgList::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
let mut active_parameter = arg_list
.generic_args()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
.count();
let first_arg_is_non_lifetime = arg_list
.generic_args()
.next()
.map_or(false, |arg| !matches!(arg, ast::GenericArg::LifetimeArg(_)));
let mut generics_def = if let Some(path) =
arg_list.syntax().ancestors().find_map(ast::Path::cast)
{
let res = sema.resolve_path(&path)?;
let generic_def: hir::GenericDef = match res {
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
hir::PathResolution::AssocItem(hir::AssocItem::Function(it)) => it.into(),
hir::PathResolution::AssocItem(hir::AssocItem::TypeAlias(it)) => it.into(),
hir::PathResolution::AssocItem(hir::AssocItem::Const(_)) => return None,
hir::PathResolution::BuiltinAttr(_)
| hir::PathResolution::ToolModule(_)
| hir::PathResolution::Local(_)
| hir::PathResolution::TypeParam(_)
| hir::PathResolution::ConstParam(_)
| hir::PathResolution::SelfType(_) => return None,
};
generic_def
} else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
{
// recv.method::<$0>()
let method = sema.resolve_method_call(&method_call)?;
method.into()
} else {
return None;
};
let mut res = SignatureHelp {
doc: None,
signature: String::new(),
parameters: vec![],
active_parameter: Some(active_parameter),
active_parameter: None,
};
let db = sema.db;
match generics_def {
hir::GenericDef::Function(it) => {
res.doc = it.docs(db).map(|it| it.into());
@ -187,8 +265,16 @@ fn signature_help_for_generics(
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
}
let params = generics_def.params(sema.db);
let num_lifetime_params =
params.iter().take_while(|param| matches!(param, GenericParam::LifetimeParam(_))).count();
if first_arg_is_non_lifetime {
// Lifetime parameters were omitted.
active_parameter += num_lifetime_params;
}
res.active_parameter = Some(active_parameter);
res.signature.push('<');
let params = generics_def.params(db);
let mut buf = String::new();
for param in params {
if let hir::GenericParam::TypeParam(ty) = param {
@ -208,8 +294,11 @@ fn signature_help_for_generics(
#[cfg(test)]
mod tests {
use std::iter;
use expect_test::{expect, Expect};
use ide_db::base_db::{fixture::ChangeFixture, FilePosition};
use stdx::format_to;
use crate::RootDatabase;
@ -233,26 +322,32 @@ fn check(ra_fixture: &str, expect: Expect) {
"#
);
let (db, position) = position(&fixture);
let call_info = crate::signature_help::signature_help(&db, position);
let actual = match call_info {
Some(call_info) => {
let docs = match &call_info.doc {
None => "".to_string(),
Some(docs) => format!("{}\n------\n", docs.as_str()),
};
let params = call_info
.parameter_labels()
.enumerate()
.map(|(i, param)| {
if Some(i) == call_info.active_parameter {
format!("<{}>", param)
} else {
param.to_string()
}
})
.collect::<Vec<_>>()
.join(", ");
format!("{}{}\n({})\n", docs, call_info.signature, params)
let sig_help = crate::signature_help::signature_help(&db, position);
let actual = match sig_help {
Some(sig_help) => {
let mut rendered = String::new();
if let Some(docs) = &sig_help.doc {
format_to!(rendered, "{}\n------\n", docs.as_str());
}
format_to!(rendered, "{}\n", sig_help.signature);
let mut offset = 0;
for (i, range) in sig_help.parameter_ranges().iter().enumerate() {
let is_active = sig_help.active_parameter == Some(i);
let start = u32::from(range.start());
let gap = start.checked_sub(offset).unwrap_or_else(|| {
panic!("parameter ranges out of order: {:?}", sig_help.parameter_ranges())
});
rendered.extend(iter::repeat(' ').take(gap as usize));
let width = u32::from(range.end() - range.start());
let marker = if is_active { '^' } else { '-' };
rendered.extend(iter::repeat(marker).take(width as usize));
offset += gap + width;
}
if !sig_help.parameter_ranges().is_empty() {
format_to!(rendered, "\n");
}
rendered
}
None => String::new(),
};
@ -268,7 +363,7 @@ fn foo(x: u32, y: u32) -> u32 {x + y}
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
(<x: u32>, y: u32)
^^^^^^ ------
"#]],
);
check(
@ -278,7 +373,7 @@ fn foo(x: u32, y: u32) -> u32 {x + y}
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
(<x: u32>, y: u32)
^^^^^^ ------
"#]],
);
check(
@ -288,7 +383,7 @@ fn foo(x: u32, y: u32) -> u32 {x + y}
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
(x: u32, <y: u32>)
------ ^^^^^^
"#]],
);
check(
@ -298,7 +393,7 @@ fn foo(x: u32, y: u32) -> u32 {x + y}
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
(x: u32, <y: u32>)
------ ^^^^^^
"#]],
);
}
@ -312,7 +407,7 @@ fn foo(x: u32, y: u32) -> u32 {x + y}
"#,
expect![[r#"
fn foo(x: u32, y: u32) -> u32
(<x: u32>, y: u32)
^^^^^^ ------
"#]],
);
}
@ -329,7 +424,7 @@ fn foo<T, U: Copy + Display>(x: T, y: U) -> u32
"#,
expect![[r#"
fn foo(x: i32, y: {unknown}) -> u32
(<x: i32>, y: {unknown})
^^^^^^ ------------
"#]],
);
}
@ -343,7 +438,6 @@ fn foo<T>() -> T where T: Copy + Display {}
"#,
expect![[r#"
fn foo() -> {unknown}
()
"#]],
);
}
@ -360,7 +454,6 @@ fn bar() {
"#,
expect![[r#"
fn new()
()
"#]],
);
}
@ -379,7 +472,6 @@ fn bar() {
"#,
expect![[r#"
fn do_it(&self)
()
"#]],
);
}
@ -397,7 +489,7 @@ fn foo(&self, x: i32) {}
"#,
expect![[r#"
fn foo(&self, x: i32)
(<x: i32>)
^^^^^^
"#]],
);
}
@ -415,7 +507,7 @@ fn foo(&self, x: T) {}
"#,
expect![[r#"
fn foo(&self, x: u32)
(<x: u32>)
^^^^^^
"#]],
);
}
@ -433,7 +525,7 @@ fn foo(&self, x: i32) {}
"#,
expect![[r#"
fn foo(self: &S, x: i32)
(<self: &S>, x: i32)
^^^^^^^^ ------
"#]],
);
}
@ -453,11 +545,11 @@ fn bar() {
}
"#,
expect![[r#"
test
------
fn foo(j: u32) -> u32
(<j: u32>)
"#]],
test
------
fn foo(j: u32) -> u32
^^^^^^
"#]],
);
}
@ -482,19 +574,19 @@ pub fn do() {
add_one($0
}"#,
expect![[r##"
Adds one to the number given.
Adds one to the number given.
# Examples
# Examples
```
let five = 5;
```
let five = 5;
assert_eq!(6, my_crate::add_one(5));
```
------
fn add_one(x: i32) -> i32
(<x: i32>)
"##]],
assert_eq!(6, my_crate::add_one(5));
```
------
fn add_one(x: i32) -> i32
^^^^^^
"##]],
);
}
@ -524,19 +616,19 @@ pub fn do_it() {
}
"#,
expect![[r##"
Adds one to the number given.
Adds one to the number given.
# Examples
# Examples
```
let five = 5;
```
let five = 5;
assert_eq!(6, my_crate::add_one(5));
```
------
fn add_one(x: i32) -> i32
(<x: i32>)
"##]],
assert_eq!(6, my_crate::add_one(5));
```
------
fn add_one(x: i32) -> i32
^^^^^^
"##]],
);
}
@ -568,13 +660,13 @@ pub fn foo(mut r: WriteHandler<()>) {
}
"#,
expect![[r#"
Method is called when writer finishes.
Method is called when writer finishes.
By default this method stops actor's `Context`.
------
fn finished(&mut self, ctx: &mut {unknown})
(<ctx: &mut {unknown}>)
"#]],
By default this method stops actor's `Context`.
------
fn finished(&mut self, ctx: &mut {unknown})
^^^^^^^^^^^^^^^^^^^
"#]],
);
}
@ -605,7 +697,7 @@ fn main() {
"#,
expect![[r#"
fn bar(&self, _: u32)
(<_: u32>)
^^^^^^
"#]],
);
}
@ -621,11 +713,11 @@ fn main() {
}
"#,
expect![[r#"
A cool tuple struct
------
struct S(u32, i32)
(u32, <i32>)
"#]],
A cool tuple struct
------
struct S(u32, i32)
--- ^^^
"#]],
);
}
@ -640,7 +732,7 @@ fn main() {
"#,
expect![[r#"
struct S({unknown})
(<{unknown}>)
^^^^^^^^^
"#]],
);
}
@ -663,11 +755,11 @@ fn main() {
}
"#,
expect![[r#"
A Variant
------
enum E::A(i32)
(<i32>)
"#]],
A Variant
------
enum E::A(i32)
^^^
"#]],
);
}
@ -717,7 +809,6 @@ fn foo() { }
"#,
expect![[r#"
fn foo()
()
"#]],
);
}
@ -734,7 +825,7 @@ fn main() {
"#,
expect![[r#"
(S) -> i32
(<S>)
^
"#]],
)
}
@ -749,7 +840,7 @@ fn main(f: fn(i32, f64) -> char) {
"#,
expect![[r#"
(i32, f64) -> char
(i32, <f64>)
--- ^^^
"#]],
)
}
@ -763,9 +854,9 @@ fn main() {
foo($0
}"#,
expect![[r#"
fn foo(foo: u32, bar: u32)
(<foo: u32>, bar: u32)
"#]],
fn foo(foo: u32, bar: u32)
^^^^^^^^ --------
"#]],
);
// check with surrounding space
check(
@ -775,12 +866,52 @@ fn main() {
foo( $0
}"#,
expect![[r#"
fn foo(foo: u32, bar: u32)
(<foo: u32>, bar: u32)
"#]],
fn foo(foo: u32, bar: u32)
^^^^^^^^ --------
"#]],
)
}
#[test]
fn test_multiline_argument() {
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
0 => 1,$0
})
}"#,
expect![[r#""#]],
);
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee(match 0 {
0 => 1,
},$0)
}"#,
expect![[r#"
fn callee(a: u8, b: u8)
----- ^^^^^
"#]],
);
check(
r#"
fn callee(a: u8, b: u8) {}
fn main() {
callee($0match 0 {
0 => 1,
})
}"#,
expect![[r#"
fn callee(a: u8, b: u8)
^^^^^ -----
"#]],
);
}
#[test]
fn test_generics_simple() {
check(
@ -799,7 +930,7 @@ fn f() {
Option docs.
------
enum Option<T>
(<T>)
^
"#]],
);
}
@ -826,7 +957,7 @@ fn f() {
None docs.
------
enum Option<T>
(<T>)
^
"#]],
);
}
@ -849,7 +980,7 @@ fn f() {
"#,
expect![[r#"
fn f<G: Tr<()>, H>
(G: Tr<()>, <H>)
--------- ^
"#]],
);
}
@ -872,7 +1003,7 @@ fn f() {
"#,
expect![[r#"
fn f<T: Tr, U>
(<T: Tr>, U)
^^^^^ -
"#]],
);
}
@ -892,9 +1023,9 @@ fn f() {
}
"#,
expect![[r#"
fn f<T>
(<T>)
"#]],
fn f<T>
^
"#]],
);
}
@ -902,16 +1033,29 @@ fn f<T>
fn test_generic_kinds() {
check(
r#"
fn callee<'a, const A: (), T, const C: u8>() {}
fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
callee::<'static, $0
}
"#,
expect![[r#"
fn callee<'a, const A: (), T, const C: u8>
('a, <const A: ()>, T, const C: u8)
"#]],
fn callee<'a, const A: u8, T, const C: u8>
-- ^^^^^^^^^^^ - -----------
"#]],
);
check(
r#"
fn callee<'a, const A: u8, T, const C: u8>() {}
fn f() {
callee::<NON_LIFETIME$0
}
"#,
expect![[r#"
fn callee<'a, const A: u8, T, const C: u8>
-- ^^^^^^^^^^^ - -----------
"#]],
);
}
}

View File

@ -272,6 +272,7 @@ fn render_resolution_simple_(
cov_mark::hit!(inserts_angle_brackets_for_generics);
item.lookup_by(local_name.clone())
.label(SmolStr::from_iter([&local_name, "<…>"]))
.trigger_call_info()
.insert_snippet(cap, format!("{}<$0>", local_name));
}
}

View File

@ -43,13 +43,21 @@ pub fn callable_for_token(
sema: &Semantics<RootDatabase>,
token: SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> {
// Find the calling expression and it's NameRef
// Find the calling expression and its NameRef
let parent = token.parent()?;
let calling_node = parent.ancestors().filter_map(ast::CallableExpr::cast).find(|it| {
it.arg_list()
.map_or(false, |it| it.syntax().text_range().contains(token.text_range().start()))
})?;
callable_for_node(sema, &calling_node, &token)
}
pub fn callable_for_node(
sema: &Semantics<RootDatabase>,
calling_node: &ast::CallableExpr,
token: &SyntaxToken,
) -> Option<(hir::Callable, Option<usize>)> {
let callable = match &calling_node {
ast::CallableExpr::Call(call) => {
let expr = call.expr()?;
@ -68,53 +76,3 @@ pub fn callable_for_token(
};
Some((callable, active_param))
}
pub fn generics_for_token(
sema: &Semantics<RootDatabase>,
token: SyntaxToken,
) -> Option<(hir::GenericDef, usize)> {
let parent = token.parent()?;
let arg_list = parent
.ancestors()
.filter_map(ast::GenericArgList::cast)
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
let active_param = arg_list
.generic_args()
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
.count();
if let Some(path) = arg_list.syntax().ancestors().find_map(ast::Path::cast) {
let res = sema.resolve_path(&path)?;
let generic_def: hir::GenericDef = match res {
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
hir::PathResolution::AssocItem(hir::AssocItem::Function(it)) => it.into(),
hir::PathResolution::AssocItem(hir::AssocItem::TypeAlias(it)) => it.into(),
hir::PathResolution::AssocItem(hir::AssocItem::Const(_)) => return None,
hir::PathResolution::BuiltinAttr(_)
| hir::PathResolution::ToolModule(_)
| hir::PathResolution::Local(_)
| hir::PathResolution::TypeParam(_)
| hir::PathResolution::ConstParam(_)
| hir::PathResolution::SelfType(_) => return None,
};
Some((generic_def, active_param))
} else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
{
// recv.method::<$0>()
let method = sema.resolve_method_call(&method_call)?;
Some((method.into(), active_param))
} else {
None
}
}