Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit 8c16b07

Browse files
bors[bot]Jonas Schievink
andauthored
11760: feat: Provide signature help when editing generic args r=jonas-schievink a=jonas-schievink ![screenshot-2022-03-18-19:48:14](https://user-images.githubusercontent.com/1786438/159067106-3917a355-ca77-4d23-ad56-945dcc945425.png) bors r+ Co-authored-by: Jonas Schievink <jonas.schievink@ferrous-systems.com>
2 parents e3217c5 + 0642724 commit 8c16b07

File tree

4 files changed

+305
-8
lines changed

4 files changed

+305
-8
lines changed

crates/hir/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2075,7 +2075,7 @@ impl GenericDef {
20752075
id: LifetimeParamId { parent: self.into(), local_id },
20762076
})
20772077
.map(GenericParam::LifetimeParam);
2078-
ty_params.chain(lt_params).collect()
2078+
lt_params.chain(ty_params).collect()
20792079
}
20802080

20812081
pub fn type_params(self, db: &dyn HirDatabase) -> Vec<TypeOrConstParam> {
@@ -2336,6 +2336,18 @@ impl TypeParam {
23362336
self.id.parent().module(db.upcast()).into()
23372337
}
23382338

2339+
/// Is this type parameter implicitly introduced (eg. `Self` in a trait or an `impl Trait`
2340+
/// argument)?
2341+
pub fn is_implicit(self, db: &dyn HirDatabase) -> bool {
2342+
let params = db.generic_params(self.id.parent());
2343+
let data = &params.type_or_consts[self.id.local_id()];
2344+
match data.type_param().unwrap().provenance {
2345+
hir_def::generics::TypeParamProvenance::TypeParamList => false,
2346+
hir_def::generics::TypeParamProvenance::TraitSelf
2347+
| hir_def::generics::TypeParamProvenance::ArgumentImplTrait => true,
2348+
}
2349+
}
2350+
23392351
pub fn ty(self, db: &dyn HirDatabase) -> Type {
23402352
let resolver = self.id.parent().resolver(db.upcast());
23412353
let krate = self.id.parent().module(db.upcast()).krate();

crates/ide/src/call_info.rs

Lines changed: 241 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
use either::Either;
44
use hir::{HasAttrs, HirDisplay, Semantics};
5-
use ide_db::{active_parameter::callable_for_token, base_db::FilePosition};
5+
use ide_db::{
6+
active_parameter::{callable_for_token, generics_for_token},
7+
base_db::FilePosition,
8+
};
69
use stdx::format_to;
710
use syntax::{algo, AstNode, Direction, TextRange, TextSize};
811

@@ -27,8 +30,16 @@ impl CallInfo {
2730
&self.parameters
2831
}
2932

30-
fn push_param(&mut self, param: &str) {
31-
if !self.signature.ends_with('(') {
33+
fn push_call_param(&mut self, param: &str) {
34+
self.push_param('(', param);
35+
}
36+
37+
fn push_generic_param(&mut self, param: &str) {
38+
self.push_param('<', param);
39+
}
40+
41+
fn push_param(&mut self, opening_delim: char, param: &str) {
42+
if !self.signature.ends_with(opening_delim) {
3243
self.signature.push_str(", ");
3344
}
3445
let start = TextSize::of(&self.signature);
@@ -51,8 +62,22 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
5162
.and_then(|tok| algo::skip_trivia_token(tok, Direction::Prev))?;
5263
let token = sema.descend_into_macros_single(token);
5364

54-
let (callable, active_parameter) = callable_for_token(&sema, token)?;
65+
if let Some((callable, active_parameter)) = callable_for_token(&sema, token.clone()) {
66+
return Some(call_info_for_callable(db, callable, active_parameter));
67+
}
68+
69+
if let Some((generic_def, active_parameter)) = generics_for_token(&sema, token.clone()) {
70+
return call_info_for_generics(db, generic_def, active_parameter);
71+
}
72+
73+
None
74+
}
5575

76+
fn call_info_for_callable(
77+
db: &RootDatabase,
78+
callable: hir::Callable,
79+
active_parameter: Option<usize>,
80+
) -> CallInfo {
5681
let mut res =
5782
CallInfo { doc: None, signature: String::new(), parameters: vec![], active_parameter };
5883

@@ -92,7 +117,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
92117
}
93118
}
94119
format_to!(buf, "{}", ty.display(db));
95-
res.push_param(&buf);
120+
res.push_call_param(&buf);
96121
}
97122
}
98123
res.signature.push(')');
@@ -106,6 +131,75 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
106131
}
107132
hir::CallableKind::TupleStruct(_) | hir::CallableKind::TupleEnumVariant(_) => {}
108133
}
134+
res
135+
}
136+
137+
fn call_info_for_generics(
138+
db: &RootDatabase,
139+
mut generics_def: hir::GenericDef,
140+
active_parameter: usize,
141+
) -> Option<CallInfo> {
142+
let mut res = CallInfo {
143+
doc: None,
144+
signature: String::new(),
145+
parameters: vec![],
146+
active_parameter: Some(active_parameter),
147+
};
148+
149+
match generics_def {
150+
hir::GenericDef::Function(it) => {
151+
res.doc = it.docs(db).map(|it| it.into());
152+
format_to!(res.signature, "fn {}", it.name(db));
153+
}
154+
hir::GenericDef::Adt(hir::Adt::Enum(it)) => {
155+
res.doc = it.docs(db).map(|it| it.into());
156+
format_to!(res.signature, "enum {}", it.name(db));
157+
}
158+
hir::GenericDef::Adt(hir::Adt::Struct(it)) => {
159+
res.doc = it.docs(db).map(|it| it.into());
160+
format_to!(res.signature, "struct {}", it.name(db));
161+
}
162+
hir::GenericDef::Adt(hir::Adt::Union(it)) => {
163+
res.doc = it.docs(db).map(|it| it.into());
164+
format_to!(res.signature, "union {}", it.name(db));
165+
}
166+
hir::GenericDef::Trait(it) => {
167+
res.doc = it.docs(db).map(|it| it.into());
168+
format_to!(res.signature, "trait {}", it.name(db));
169+
}
170+
hir::GenericDef::TypeAlias(it) => {
171+
res.doc = it.docs(db).map(|it| it.into());
172+
format_to!(res.signature, "type {}", it.name(db));
173+
}
174+
hir::GenericDef::Variant(it) => {
175+
// In paths, generics of an enum can be specified *after* one of its variants.
176+
// eg. `None::<u8>`
177+
// We'll use the signature of the enum, but include the docs of the variant.
178+
res.doc = it.docs(db).map(|it| it.into());
179+
let it = it.parent_enum(db);
180+
format_to!(res.signature, "enum {}", it.name(db));
181+
generics_def = it.into();
182+
}
183+
// These don't have generic args that can be specified
184+
hir::GenericDef::Impl(_) | hir::GenericDef::Const(_) => return None,
185+
}
186+
187+
res.signature.push('<');
188+
let params = generics_def.params(db);
189+
let mut buf = String::new();
190+
for param in params {
191+
if let hir::GenericParam::TypeParam(ty) = param {
192+
if ty.is_implicit(db) {
193+
continue;
194+
}
195+
}
196+
197+
buf.clear();
198+
format_to!(buf, "{}", param.display(db));
199+
res.push_generic_param(&buf);
200+
}
201+
res.signature.push('>');
202+
109203
Some(res)
110204
}
111205

@@ -128,7 +222,14 @@ mod tests {
128222
}
129223

130224
fn check(ra_fixture: &str, expect: Expect) {
131-
let (db, position) = position(ra_fixture);
225+
// Implicitly add `Sized` to avoid noisy `T: ?Sized` in the results.
226+
let fixture = format!(
227+
r#"
228+
#[lang = "sized"] trait Sized {{}}
229+
{ra_fixture}
230+
"#
231+
);
232+
let (db, position) = position(&fixture);
132233
let call_info = crate::call_info::call_info(&db, position);
133234
let actual = match call_info {
134235
Some(call_info) => {
@@ -676,4 +777,138 @@ fn main() {
676777
"#]],
677778
)
678779
}
780+
781+
#[test]
782+
fn test_generics_simple() {
783+
check(
784+
r#"
785+
/// Option docs.
786+
enum Option<T> {
787+
Some(T),
788+
None,
789+
}
790+
791+
fn f() {
792+
let opt: Option<$0
793+
}
794+
"#,
795+
expect![[r#"
796+
Option docs.
797+
------
798+
enum Option<T>
799+
(<T>)
800+
"#]],
801+
);
802+
}
803+
804+
#[test]
805+
fn test_generics_on_variant() {
806+
check(
807+
r#"
808+
/// Option docs.
809+
enum Option<T> {
810+
/// Some docs.
811+
Some(T),
812+
/// None docs.
813+
None,
814+
}
815+
816+
use Option::*;
817+
818+
fn f() {
819+
None::<$0
820+
}
821+
"#,
822+
expect![[r#"
823+
None docs.
824+
------
825+
enum Option<T>
826+
(<T>)
827+
"#]],
828+
);
829+
}
830+
831+
#[test]
832+
fn test_lots_of_generics() {
833+
check(
834+
r#"
835+
trait Tr<T> {}
836+
837+
struct S<T>(T);
838+
839+
impl<T> S<T> {
840+
fn f<G, H>(g: G, h: impl Tr<G>) where G: Tr<()> {}
841+
}
842+
843+
fn f() {
844+
S::<u8>::f::<(), $0
845+
}
846+
"#,
847+
expect![[r#"
848+
fn f<G: Tr<()>, H>
849+
(G: Tr<()>, <H>)
850+
"#]],
851+
);
852+
}
853+
854+
#[test]
855+
fn test_generics_in_trait_ufcs() {
856+
check(
857+
r#"
858+
trait Tr {
859+
fn f<T: Tr, U>() {}
860+
}
861+
862+
struct S;
863+
864+
impl Tr for S {}
865+
866+
fn f() {
867+
<S as Tr>::f::<$0
868+
}
869+
"#,
870+
expect![[r#"
871+
fn f<T: Tr, U>
872+
(<T: Tr>, U)
873+
"#]],
874+
);
875+
}
876+
877+
#[test]
878+
fn test_generics_in_method_call() {
879+
check(
880+
r#"
881+
struct S;
882+
883+
impl S {
884+
fn f<T>(&self) {}
885+
}
886+
887+
fn f() {
888+
S.f::<$0
889+
}
890+
"#,
891+
expect![[r#"
892+
fn f<T>
893+
(<T>)
894+
"#]],
895+
);
896+
}
897+
898+
#[test]
899+
fn test_generic_kinds() {
900+
check(
901+
r#"
902+
fn callee<'a, const A: (), T, const C: u8>() {}
903+
904+
fn f() {
905+
callee::<'static, $0
906+
}
907+
"#,
908+
expect![[r#"
909+
fn callee<'a, const A: (), T, const C: u8>
910+
('a, <const A: ()>, T, const C: u8)
911+
"#]],
912+
);
913+
}
679914
}

crates/ide_db/src/active_parameter.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,53 @@ pub fn callable_for_token(
6868
};
6969
Some((callable, active_param))
7070
}
71+
72+
pub fn generics_for_token(
73+
sema: &Semantics<RootDatabase>,
74+
token: SyntaxToken,
75+
) -> Option<(hir::GenericDef, usize)> {
76+
let parent = token.parent()?;
77+
let arg_list = parent
78+
.ancestors()
79+
.filter_map(ast::GenericArgList::cast)
80+
.find(|list| list.syntax().text_range().contains(token.text_range().start()))?;
81+
82+
let active_param = arg_list
83+
.generic_args()
84+
.take_while(|arg| arg.syntax().text_range().end() <= token.text_range().start())
85+
.count();
86+
87+
if let Some(path) = arg_list.syntax().ancestors().find_map(ast::Path::cast) {
88+
let res = sema.resolve_path(&path)?;
89+
let generic_def: hir::GenericDef = match res {
90+
hir::PathResolution::Def(hir::ModuleDef::Adt(it)) => it.into(),
91+
hir::PathResolution::Def(hir::ModuleDef::Function(it)) => it.into(),
92+
hir::PathResolution::Def(hir::ModuleDef::Trait(it)) => it.into(),
93+
hir::PathResolution::Def(hir::ModuleDef::TypeAlias(it)) => it.into(),
94+
hir::PathResolution::Def(hir::ModuleDef::Variant(it)) => it.into(),
95+
hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_))
96+
| hir::PathResolution::Def(hir::ModuleDef::Const(_))
97+
| hir::PathResolution::Def(hir::ModuleDef::Macro(_))
98+
| hir::PathResolution::Def(hir::ModuleDef::Module(_))
99+
| hir::PathResolution::Def(hir::ModuleDef::Static(_)) => return None,
100+
hir::PathResolution::AssocItem(hir::AssocItem::Function(it)) => it.into(),
101+
hir::PathResolution::AssocItem(hir::AssocItem::TypeAlias(it)) => it.into(),
102+
hir::PathResolution::AssocItem(hir::AssocItem::Const(_)) => return None,
103+
hir::PathResolution::BuiltinAttr(_)
104+
| hir::PathResolution::ToolModule(_)
105+
| hir::PathResolution::Local(_)
106+
| hir::PathResolution::TypeParam(_)
107+
| hir::PathResolution::ConstParam(_)
108+
| hir::PathResolution::SelfType(_) => return None,
109+
};
110+
111+
Some((generic_def, active_param))
112+
} else if let Some(method_call) = arg_list.syntax().parent().and_then(ast::MethodCallExpr::cast)
113+
{
114+
// recv.method::<$0>()
115+
let method = sema.resolve_method_call(&method_call)?;
116+
Some((method.into(), active_param))
117+
} else {
118+
None
119+
}
120+
}

crates/rust-analyzer/src/caps.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn server_capabilities(config: &Config) -> ServerCapabilities {
3434
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
3535
}),
3636
signature_help_provider: Some(SignatureHelpOptions {
37-
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
37+
trigger_characters: Some(vec!["(".to_string(), ",".to_string(), "<".to_string()]),
3838
retrigger_characters: None,
3939
work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None },
4040
}),

0 commit comments

Comments
 (0)