diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2022-03-18 19:01:25 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-18 19:01:25 +0000 |
| commit | 8c16b07c0733ce305d3bf92ef0aaf45cfd9977e9 (patch) | |
| tree | a6eaf23aad7f0e70e81108f1972e02f84fbed5bf | |
| parent | e3217c50150e369aed845fc00103d78e1b1b7a74 (diff) | |
| parent | 0642724e94f032268d74d90b97752ad4717004b5 (diff) | |
| download | rust-8c16b07c0733ce305d3bf92ef0aaf45cfd9977e9.tar.gz rust-8c16b07c0733ce305d3bf92ef0aaf45cfd9977e9.zip | |
Merge #11760
11760: feat: Provide signature help when editing generic args r=jonas-schievink a=jonas-schievink  bors r+ Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
| -rw-r--r-- | crates/hir/src/lib.rs | 14 | ||||
| -rw-r--r-- | crates/ide/src/call_info.rs | 247 | ||||
| -rw-r--r-- | crates/ide_db/src/active_parameter.rs | 50 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/caps.rs | 2 |
4 files changed, 305 insertions, 8 deletions
diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 56832d6f8b3..3c12907b824 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -2075,7 +2075,7 @@ impl GenericDef { id: LifetimeParamId { parent: self.into(), local_id }, }) .map(GenericParam::LifetimeParam); - ty_params.chain(lt_params).collect() + lt_params.chain(ty_params).collect() } pub fn type_params(self, db: &dyn HirDatabase) -> Vec<TypeOrConstParam> { @@ -2336,6 +2336,18 @@ impl TypeParam { self.id.parent().module(db.upcast()).into() } + /// Is this type parameter implicitly introduced (eg. `Self` in a trait or an `impl Trait` + /// argument)? + pub fn is_implicit(self, db: &dyn HirDatabase) -> bool { + let params = db.generic_params(self.id.parent()); + let data = ¶ms.type_or_consts[self.id.local_id()]; + match data.type_param().unwrap().provenance { + hir_def::generics::TypeParamProvenance::TypeParamList => false, + hir_def::generics::TypeParamProvenance::TraitSelf + | hir_def::generics::TypeParamProvenance::ArgumentImplTrait => true, + } + } + pub fn ty(self, db: &dyn HirDatabase) -> Type { let resolver = self.id.parent().resolver(db.upcast()); let krate = self.id.parent().module(db.upcast()).krate(); diff --git a/crates/ide/src/call_info.rs b/crates/ide/src/call_info.rs index 7568faa6bd5..ad831f0f949 100644 --- a/crates/ide/src/call_info.rs +++ b/crates/ide/src/call_info.rs @@ -2,7 +2,10 @@ use either::Either; use hir::{HasAttrs, HirDisplay, Semantics}; -use ide_db::{active_parameter::callable_for_token, base_db::FilePosition}; +use ide_db::{ + active_parameter::{callable_for_token, generics_for_token}, + base_db::FilePosition, +}; use stdx::format_to; use syntax::{algo, AstNode, Direction, TextRange, TextSize}; @@ -27,8 +30,16 @@ impl CallInfo { &self.parameters } - fn push_param(&mut self, param: &str) { - if !self.signature.ends_with('(') { + fn push_call_param(&mut self, param: &str) { + self.push_param('(', param); + } + + fn push_generic_param(&mut self, param: &str) { + self.push_param('<', param); + } + + fn push_param(&mut self, opening_delim: char, param: &str) { + if !self.signature.ends_with(opening_delim) { self.signature.push_str(", "); } let start = TextSize::of(&self.signature); @@ -51,8 +62,22 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal .and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?; let token = sema.descend_into_macros_single(token); - let (callable, active_parameter) = callable_for_token(&sema, token)?; + if let Some((callable, active_parameter)) = callable_for_token(&sema, token.clone()) { + return Some(call_info_for_callable(db, callable, active_parameter)); + } + + if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) { + return call_info_for_generics(db, generic_def, active_parameter); + } + + None +} +fn call_info_for_callable( + db: &RootDatabase, + callable: hir::Callable, + active_parameter: Option<usize>, +) -> CallInfo { let mut res = CallInfo { doc: None, signature: String::new(), parameters: vec![], active_parameter }; @@ -92,7 +117,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal } } format_to!(buf, "{}", ty.display(db)); - res.push_param(&buf); + res.push_call_param(&buf); } } res.signature.push(')'); @@ -106,6 +131,75 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal } hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {} } + res +} + +fn call_info_for_generics( + db: &RootDatabase, + mut generics_def: hir::GenericDef, + active_parameter: usize, +) -> Option<CallInfo> { + let mut res = CallInfo { + doc: None, + signature: String::new(), + parameters: vec![], + active_parameter: Some(active_parameter), + }; + + match generics_def { + hir::GenericDef::Function(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "fn {}", it.name(db)); + } + hir::GenericDef::Adt(hir::Adt::Enum(it)) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "enum {}", it.name(db)); + } + hir::GenericDef::Adt(hir::Adt::Struct(it)) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "struct {}", it.name(db)); + } + hir::GenericDef::Adt(hir::Adt::Union(it)) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "union {}", it.name(db)); + } + hir::GenericDef::Trait(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "trait {}", it.name(db)); + } + hir::GenericDef::TypeAlias(it) => { + res.doc = it.docs(db).map(|it| it.into()); + format_to!(res.signature, "type {}", it.name(db)); + } + hir::GenericDef::Variant(it) => { + // In paths, generics of an enum can be specified *after* one of its variants. + // eg. `None::<u8>` + // We'll use the signature of the enum, but include the docs of the variant. + res.doc = it.docs(db).map(|it| it.into()); + let it = it.parent_enum(db); + format_to!(res.signature, "enum {}", it.name(db)); + generics_def = it.into(); + } + // These don't have generic args that can be specified + hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None, + } + + 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 { + if ty.is_implicit(db) { + continue; + } + } + + buf.clear(); + format_to!(buf, "{}", param.display(db)); + res.push_generic_param(&buf); + } + res.signature.push('>'); + Some(res) } @@ -128,7 +222,14 @@ mod tests { } fn check(ra_fixture: &str, expect: Expect) { - let (db, position) = position(ra_fixture); + // Implicitly add `Sized` to avoid noisy `T: ?Sized` in the results. + let fixture = format!( + r#" +#[lang = "sized"] trait Sized {{}} +{ra_fixture} + "# + ); + let (db, position) = position(&fixture); let call_info = crate::call_info::call_info(&db, position); let actual = match call_info { Some(call_info) => { @@ -676,4 +777,138 @@ fn main() { "#]], ) } + + #[test] + fn test_generics_simple() { + check( + r#" +/// Option docs. +enum Option<T> { + Some(T), + None, +} + +fn f() { + let opt: Option<$0 +} + "#, + expect![[r#" + Option docs. + ------ + enum Option<T> + (<T>) + "#]], + ); + } + + #[test] + fn test_generics_on_variant() { + check( + r#" +/// Option docs. +enum Option<T> { + /// Some docs. + Some(T), + /// None docs. + None, +} + +use Option::*; + +fn f() { + None::<$0 +} + "#, + expect![[r#" + None docs. + ------ + enum Option<T> + (<T>) + "#]], + ); + } + + #[test] + fn test_lots_of_generics() { + check( + r#" +trait Tr<T> {} + +struct S<T>(T); + +impl<T> S<T> { + fn f<G, H>(g: G, h: impl Tr<G>) where G: Tr<()> {} +} + +fn f() { + S::<u8>::f::<(), $0 +} + "#, + expect![[r#" + fn f<G: Tr<()>, H> + (G: Tr<()>, <H>) + "#]], + ); + } + + #[test] + fn test_generics_in_trait_ufcs() { + check( + r#" +trait Tr { + fn f<T: Tr, U>() {} +} + +struct S; + +impl Tr for S {} + +fn f() { + <S as Tr>::f::<$0 +} + "#, + expect![[r#" + fn f<T: Tr, U> + (<T: Tr>, U) + "#]], + ); + } + + #[test] + fn test_generics_in_method_call() { + check( + r#" +struct S; + +impl S { + fn f<T>(&self) {} +} + +fn f() { + S.f::<$0 +} + "#, + expect![[r#" + fn f<T> + (<T>) + "#]], + ); + } + + #[test] + fn test_generic_kinds() { + check( + r#" +fn callee<'a, const A: (), 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) + "#]], + ); + } } diff --git a/crates/ide_db/src/active_parameter.rs b/crates/ide_db/src/active_parameter.rs index 47bd7aa9798..67b819c5a5d 100644 --- a/crates/ide_db/src/active_parameter.rs +++ b/crates/ide_db/src/active_parameter.rs @@ -68,3 +68,53 @@ 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 + } +} diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index dc6cf61f79a..d83af2a48a9 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -34,7 +34,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities { work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, }), signature_help_provider: Some(SignatureHelpOptions { - trigger_characters: Some(vec!["(".to_string(), ",".to_string()]), + trigger_characters: Some(vec!["(".to_string(), ",".to_string(), "<".to_string()]), retrigger_characters: None, work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None }, }), |
