Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/signature_help.rs')
| -rw-r--r-- | crates/ide/src/signature_help.rs | 213 |
1 files changed, 171 insertions, 42 deletions
diff --git a/crates/ide/src/signature_help.rs b/crates/ide/src/signature_help.rs index b5468a5aee..0e17b35590 100644 --- a/crates/ide/src/signature_help.rs +++ b/crates/ide/src/signature_help.rs @@ -5,20 +5,22 @@ use std::collections::BTreeSet; use either::Either; use hir::{ - AssocItem, DisplayTarget, GenericParam, HirDisplay, ModuleDef, PathResolution, Semantics, Trait, + AssocItem, DisplayTarget, GenericDef, GenericParam, HirDisplay, ModuleDef, PathResolution, + Semantics, Trait, }; use ide_db::{ - active_parameter::{callable_for_node, generic_def_for_node}, - documentation::{Documentation, HasDocs}, FilePosition, FxIndexMap, + active_parameter::{callable_for_arg_list, generic_def_for_node}, + documentation::{Documentation, HasDocs}, }; +use itertools::Itertools; use span::Edition; use stdx::format_to; use syntax::{ - algo, - ast::{self, AstChildren, HasArgList}, - match_ast, AstNode, Direction, NodeOrToken, SyntaxElementChildren, SyntaxNode, SyntaxToken, - TextRange, TextSize, ToSmolStr, T, + AstNode, Direction, NodeOrToken, SyntaxElementChildren, SyntaxNode, SyntaxToken, T, TextRange, + TextSize, ToSmolStr, algo, + ast::{self, AstChildren}, + match_ast, }; use crate::RootDatabase; @@ -83,8 +85,8 @@ pub(crate) fn signature_help( .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; let token = sema.descend_into_macros_single_exact(token); let edition = - sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT); - let display_target = sema.first_crate_or_default(file_id).to_display_target(db); + sema.attach_first_edition(file_id).map(|it| it.edition(db)).unwrap_or(Edition::CURRENT); + let display_target = sema.first_crate(file_id)?.to_display_target(db); for node in token.parent_ancestors() { match_ast! { @@ -163,20 +165,8 @@ fn signature_help_for_call( edition: Edition, display_target: DisplayTarget, ) -> Option<SignatureHelp> { - // Find the calling expression and its NameRef - let mut nodes = arg_list.syntax().ancestors().skip(1); - let calling_node = loop { - if let Some(callable) = ast::CallableExpr::cast(nodes.next()?) { - let inside_callable = callable - .arg_list() - .is_some_and(|it| it.syntax().text_range().contains(token.text_range().start())); - if inside_callable { - break callable; - } - } - }; - - let (callable, active_parameter) = callable_for_node(sema, &calling_node, &token)?; + let (callable, active_parameter) = + callable_for_arg_list(sema, arg_list, token.text_range().start())?; let mut res = SignatureHelp { doc: None, signature: String::new(), parameters: vec![], active_parameter }; @@ -187,6 +177,20 @@ fn signature_help_for_call( hir::CallableKind::Function(func) => { res.doc = func.docs(db); format_to!(res.signature, "fn {}", func.name(db).display(db, edition)); + + let generic_params = GenericDef::Function(func) + .params(db) + .iter() + .filter(|param| match param { + GenericParam::TypeParam(type_param) => !type_param.is_implicit(db), + GenericParam::ConstParam(_) | GenericParam::LifetimeParam(_) => true, + }) + .map(|param| param.display(db, display_target)) + .join(", "); + if !generic_params.is_empty() { + format_to!(res.signature, "<{}>", generic_params); + } + fn_params = Some(match callable.receiver_param(db) { Some(_self) => func.params_without_self(db), None => func.assoc_fn_params(db), @@ -195,15 +199,34 @@ fn signature_help_for_call( hir::CallableKind::TupleStruct(strukt) => { res.doc = strukt.docs(db); format_to!(res.signature, "struct {}", strukt.name(db).display(db, edition)); + + let generic_params = GenericDef::Adt(strukt.into()) + .params(db) + .iter() + .map(|param| param.display(db, display_target)) + .join(", "); + if !generic_params.is_empty() { + format_to!(res.signature, "<{}>", generic_params); + } } hir::CallableKind::TupleEnumVariant(variant) => { res.doc = variant.docs(db); format_to!( res.signature, - "enum {}::{}", + "enum {}", variant.parent_enum(db).name(db).display(db, edition), - variant.name(db).display(db, edition) ); + + let generic_params = GenericDef::Adt(variant.parent_enum(db).into()) + .params(db) + .iter() + .map(|param| param.display(db, display_target)) + .join(", "); + if !generic_params.is_empty() { + format_to!(res.signature, "<{}>", generic_params); + } + + format_to!(res.signature, "::{}", variant.name(db).display(db, edition)) } hir::CallableKind::Closure(closure) => { let fn_trait = closure.fn_trait(db); @@ -327,7 +350,7 @@ fn signature_help_for_generics( } // These don't have generic args that can be specified hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) | hir::GenericDef::Static(_) => { - return None + return None; } } @@ -351,6 +374,20 @@ fn signature_help_for_generics( buf.clear(); format_to!(buf, "{}", param.display(db, display_target)); + match param { + GenericParam::TypeParam(param) => { + if let Some(ty) = param.default(db) { + format_to!(buf, " = {}", ty.display(db, display_target)); + } + } + GenericParam::ConstParam(param) => { + if let Some(expr) = param.default(db, display_target).and_then(|konst| konst.expr()) + { + format_to!(buf, " = {}", expr); + } + } + _ => {} + } res.push_generic_param(&buf); } if let hir::GenericDef::Trait(tr) = generics_def { @@ -695,9 +732,8 @@ fn signature_help_for_tuple_pat_ish( } #[cfg(test)] mod tests { - use std::iter; - use expect_test::{expect, Expect}; + use expect_test::{Expect, expect}; use ide_db::FilePosition; use stdx::format_to; use test_fixture::ChangeFixture; @@ -708,13 +744,14 @@ mod tests { pub(crate) fn position( #[rust_analyzer::rust_fixture] ra_fixture: &str, ) -> (RootDatabase, FilePosition) { - let change_fixture = ChangeFixture::parse(ra_fixture); let mut database = RootDatabase::default(); + let change_fixture = ChangeFixture::parse(&database, ra_fixture); database.apply_change(change_fixture.change); let (file_id, range_or_offset) = change_fixture.file_position.expect("expected a marker ($0)"); let offset = range_or_offset.expect_offset(); - (database, FilePosition { file_id: file_id.into(), offset }) + let position = FilePosition { file_id: file_id.file_id(&database), offset }; + (database, position) } #[track_caller] @@ -742,11 +779,11 @@ mod tests { 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)); + rendered.extend(std::iter::repeat_n(' ', gap as usize)); let param_text = &sig_help.signature[*range]; let width = param_text.chars().count(); // … let marker = if is_active { '^' } else { '-' }; - rendered.extend(iter::repeat(marker).take(width)); + rendered.extend(std::iter::repeat_n(marker, width)); offset += gap + u32::from(range.len()); } if !sig_help.parameter_ranges().is_empty() { @@ -828,8 +865,8 @@ fn foo<T, U: Copy + Display>(x: T, y: U) -> u32 fn bar() { foo($03, ); } "#, expect![[r#" - fn foo(x: i32, y: U) -> u32 - ^^^^^^ ---- + fn foo<T, U>(x: i32, y: U) -> u32 + ^^^^^^ ---- "#]], ); } @@ -842,7 +879,7 @@ fn foo<T>() -> T where T: Copy + Display {} fn bar() { foo($0); } "#, expect![[r#" - fn foo() -> T + fn foo<T>() -> T "#]], ); } @@ -1292,8 +1329,8 @@ fn main() { } "#, expect![[r#" - struct S({unknown}) - ^^^^^^^^^ + struct S<T>({unknown}) + ^^^^^^^^^ "#]], ); } @@ -1388,7 +1425,7 @@ id! { fn test() { S.foo($0); } "#, expect![[r#" - fn foo(&'a mut self) + fn foo<'a>(&'a mut self) "#]], ); } @@ -1737,8 +1774,8 @@ fn sup() { } "#, expect![[r#" - fn test(&mut self, val: V) - ^^^^^^ + fn test<V>(&mut self, val: V) + ^^^^^^ "#]], ); } @@ -1914,8 +1951,8 @@ fn f() { } "#, expect![[r#" - fn foo(x: Wrap<impl Trait<U>>) - ^^^^^^^^^^^^^^^^^^^^^^ + fn foo<U>(x: Wrap<impl Trait<U>>) + ^^^^^^^^^^^^^^^^^^^^^^ "#]], ); } @@ -2407,4 +2444,96 @@ fn main() { "#]], ); } + + #[test] + fn test_tuple_generic_param() { + check( + r#" +struct S<T>(T); + +fn main() { + let s: S<$0 +} + "#, + expect![[r#" + struct S<T> + ^ + "#]], + ); + } + + #[test] + fn test_enum_generic_param() { + check( + r#" +enum Option<T> { + Some(T), + None, +} + +fn main() { + let opt: Option<$0 +} + "#, + expect![[r#" + enum Option<T> + ^ + "#]], + ); + } + + #[test] + fn test_enum_variant_generic_param() { + check( + r#" +enum Option<T> { + Some(T), + None, +} + +fn main() { + let opt = Option::Some($0); +} + "#, + expect![[r#" + enum Option<T>::Some({unknown}) + ^^^^^^^^^ + "#]], + ); + } + + #[test] + fn test_generic_arg_with_default() { + check( + r#" +struct S<T = u8> { + field: T, +} + +fn main() { + let s: S<$0 +} + "#, + expect![[r#" + struct S<T = u8> + ^^^^^^ + "#]], + ); + + check( + r#" +struct S<const C: u8 = 5> { + field: C, +} + +fn main() { + let s: S<$0 +} + "#, + expect![[r#" + struct S<const C: u8 = 5> + ^^^^^^^^^^^^^^^ + "#]], + ); + } } |