diff options
-rw-r--r-- | crates/assists/src/handlers/qualify_path.rs | 182 | ||||
-rw-r--r-- | crates/ide/src/inlay_hints.rs | 108 |
2 files changed, 275 insertions, 15 deletions
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs index f436bdbbf..d5bc4e574 100644 --- a/crates/assists/src/handlers/qualify_path.rs +++ b/crates/assists/src/handlers/qualify_path.rs | |||
@@ -56,12 +56,14 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
56 | ImportCandidate::QualifierStart(_) => { | 56 | ImportCandidate::QualifierStart(_) => { |
57 | mark::hit!(qualify_path_qualifier_start); | 57 | mark::hit!(qualify_path_qualifier_start); |
58 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; | 58 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; |
59 | let segment = path.segment()?; | 59 | let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?); |
60 | QualifyCandidate::QualifierStart(segment) | 60 | QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list()) |
61 | } | 61 | } |
62 | ImportCandidate::UnqualifiedName(_) => { | 62 | ImportCandidate::UnqualifiedName(_) => { |
63 | mark::hit!(qualify_path_unqualified_name); | 63 | mark::hit!(qualify_path_unqualified_name); |
64 | QualifyCandidate::UnqualifiedName | 64 | let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?; |
65 | let generics = path.segment()?.generic_arg_list(); | ||
66 | QualifyCandidate::UnqualifiedName(generics) | ||
65 | } | 67 | } |
66 | ImportCandidate::TraitAssocItem(_) => { | 68 | ImportCandidate::TraitAssocItem(_) => { |
67 | mark::hit!(qualify_path_trait_assoc_item); | 69 | mark::hit!(qualify_path_trait_assoc_item); |
@@ -96,22 +98,25 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
96 | } | 98 | } |
97 | 99 | ||
98 | enum QualifyCandidate<'db> { | 100 | enum QualifyCandidate<'db> { |
99 | QualifierStart(ast::PathSegment), | 101 | QualifierStart(ast::PathSegment, Option<ast::GenericArgList>), |
100 | UnqualifiedName, | 102 | UnqualifiedName(Option<ast::GenericArgList>), |
101 | TraitAssocItem(ast::Path, ast::PathSegment), | 103 | TraitAssocItem(ast::Path, ast::PathSegment), |
102 | TraitMethod(&'db RootDatabase, ast::MethodCallExpr), | 104 | TraitMethod(&'db RootDatabase, ast::MethodCallExpr), |
103 | } | 105 | } |
104 | 106 | ||
105 | impl QualifyCandidate<'_> { | 107 | impl QualifyCandidate<'_> { |
106 | fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { | 108 | fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) { |
109 | let import = mod_path_to_ast(&import); | ||
107 | match self { | 110 | match self { |
108 | QualifyCandidate::QualifierStart(segment) => { | 111 | QualifyCandidate::QualifierStart(segment, generics) => { |
109 | let import = mod_path_to_ast(&import); | 112 | let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); |
110 | replacer(format!("{}::{}", import, segment)); | 113 | replacer(format!("{}{}::{}", import, generics, segment)); |
114 | } | ||
115 | QualifyCandidate::UnqualifiedName(generics) => { | ||
116 | let generics = generics.as_ref().map_or_else(String::new, ToString::to_string); | ||
117 | replacer(format!("{}{}", import.to_string(), generics)); | ||
111 | } | 118 | } |
112 | QualifyCandidate::UnqualifiedName => replacer(mod_path_to_ast(&import).to_string()), | ||
113 | QualifyCandidate::TraitAssocItem(qualifier, segment) => { | 119 | QualifyCandidate::TraitAssocItem(qualifier, segment) => { |
114 | let import = mod_path_to_ast(&import); | ||
115 | replacer(format!("<{} as {}>::{}", qualifier, import, segment)); | 120 | replacer(format!("<{} as {}>::{}", qualifier, import, segment)); |
116 | } | 121 | } |
117 | &QualifyCandidate::TraitMethod(db, ref mcall_expr) => { | 122 | &QualifyCandidate::TraitMethod(db, ref mcall_expr) => { |
@@ -124,25 +129,27 @@ impl QualifyCandidate<'_> { | |||
124 | db: &RootDatabase, | 129 | db: &RootDatabase, |
125 | mcall_expr: &ast::MethodCallExpr, | 130 | mcall_expr: &ast::MethodCallExpr, |
126 | mut replacer: impl FnMut(String), | 131 | mut replacer: impl FnMut(String), |
127 | import: hir::ModPath, | 132 | import: ast::Path, |
128 | item: hir::ItemInNs, | 133 | item: hir::ItemInNs, |
129 | ) -> Option<()> { | 134 | ) -> Option<()> { |
130 | let receiver = mcall_expr.receiver()?; | 135 | let receiver = mcall_expr.receiver()?; |
131 | let trait_method_name = mcall_expr.name_ref()?; | 136 | let trait_method_name = mcall_expr.name_ref()?; |
137 | let generics = | ||
138 | mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string); | ||
132 | let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); | 139 | let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args()); |
133 | let trait_ = item_as_trait(item)?; | 140 | let trait_ = item_as_trait(item)?; |
134 | let method = find_trait_method(db, trait_, &trait_method_name)?; | 141 | let method = find_trait_method(db, trait_, &trait_method_name)?; |
135 | if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { | 142 | if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) { |
136 | let import = mod_path_to_ast(&import); | ||
137 | let receiver = match self_access { | 143 | let receiver = match self_access { |
138 | hir::Access::Shared => make::expr_ref(receiver, false), | 144 | hir::Access::Shared => make::expr_ref(receiver, false), |
139 | hir::Access::Exclusive => make::expr_ref(receiver, true), | 145 | hir::Access::Exclusive => make::expr_ref(receiver, true), |
140 | hir::Access::Owned => receiver, | 146 | hir::Access::Owned => receiver, |
141 | }; | 147 | }; |
142 | replacer(format!( | 148 | replacer(format!( |
143 | "{}::{}{}", | 149 | "{}::{}{}{}", |
144 | import, | 150 | import, |
145 | trait_method_name, | 151 | trait_method_name, |
152 | generics, | ||
146 | match arg_list.clone() { | 153 | match arg_list.clone() { |
147 | Some(args) => make::arg_list(iter::once(receiver).chain(args)), | 154 | Some(args) => make::arg_list(iter::once(receiver).chain(args)), |
148 | None => make::arg_list(iter::once(receiver)), | 155 | None => make::arg_list(iter::once(receiver)), |
@@ -1045,4 +1052,153 @@ fn main() { | |||
1045 | ", | 1052 | ", |
1046 | ); | 1053 | ); |
1047 | } | 1054 | } |
1055 | |||
1056 | #[test] | ||
1057 | fn keep_generic_annotations() { | ||
1058 | check_assist( | ||
1059 | qualify_path, | ||
1060 | r" | ||
1061 | //- /lib.rs crate:dep | ||
1062 | pub mod generic { pub struct Thing<'a, T>(&'a T); } | ||
1063 | |||
1064 | //- /main.rs crate:main deps:dep | ||
1065 | fn foo() -> Thin<|>g<'static, ()> {} | ||
1066 | |||
1067 | fn main() {} | ||
1068 | ", | ||
1069 | r" | ||
1070 | fn foo() -> dep::generic::Thing<'static, ()> {} | ||
1071 | |||
1072 | fn main() {} | ||
1073 | ", | ||
1074 | ); | ||
1075 | } | ||
1076 | |||
1077 | #[test] | ||
1078 | fn keep_generic_annotations_leading_colon() { | ||
1079 | check_assist( | ||
1080 | qualify_path, | ||
1081 | r" | ||
1082 | //- /lib.rs crate:dep | ||
1083 | pub mod generic { pub struct Thing<'a, T>(&'a T); } | ||
1084 | |||
1085 | //- /main.rs crate:main deps:dep | ||
1086 | fn foo() -> Thin<|>g::<'static, ()> {} | ||
1087 | |||
1088 | fn main() {} | ||
1089 | ", | ||
1090 | r" | ||
1091 | fn foo() -> dep::generic::Thing::<'static, ()> {} | ||
1092 | |||
1093 | fn main() {} | ||
1094 | ", | ||
1095 | ); | ||
1096 | } | ||
1097 | |||
1098 | #[test] | ||
1099 | fn associated_struct_const_generic() { | ||
1100 | check_assist( | ||
1101 | qualify_path, | ||
1102 | r" | ||
1103 | mod test_mod { | ||
1104 | pub struct TestStruct<T> {} | ||
1105 | impl<T> TestStruct<T> { | ||
1106 | const TEST_CONST: u8 = 42; | ||
1107 | } | ||
1108 | } | ||
1109 | |||
1110 | fn main() { | ||
1111 | TestStruct::<()>::TEST_CONST<|> | ||
1112 | } | ||
1113 | ", | ||
1114 | r" | ||
1115 | mod test_mod { | ||
1116 | pub struct TestStruct<T> {} | ||
1117 | impl<T> TestStruct<T> { | ||
1118 | const TEST_CONST: u8 = 42; | ||
1119 | } | ||
1120 | } | ||
1121 | |||
1122 | fn main() { | ||
1123 | test_mod::TestStruct::<()>::TEST_CONST | ||
1124 | } | ||
1125 | ", | ||
1126 | ); | ||
1127 | } | ||
1128 | |||
1129 | #[test] | ||
1130 | fn associated_trait_const_generic() { | ||
1131 | check_assist( | ||
1132 | qualify_path, | ||
1133 | r" | ||
1134 | mod test_mod { | ||
1135 | pub trait TestTrait { | ||
1136 | const TEST_CONST: u8; | ||
1137 | } | ||
1138 | pub struct TestStruct<T> {} | ||
1139 | impl<T> TestTrait for TestStruct<T> { | ||
1140 | const TEST_CONST: u8 = 42; | ||
1141 | } | ||
1142 | } | ||
1143 | |||
1144 | fn main() { | ||
1145 | test_mod::TestStruct::<()>::TEST_CONST<|> | ||
1146 | } | ||
1147 | ", | ||
1148 | r" | ||
1149 | mod test_mod { | ||
1150 | pub trait TestTrait { | ||
1151 | const TEST_CONST: u8; | ||
1152 | } | ||
1153 | pub struct TestStruct<T> {} | ||
1154 | impl<T> TestTrait for TestStruct<T> { | ||
1155 | const TEST_CONST: u8 = 42; | ||
1156 | } | ||
1157 | } | ||
1158 | |||
1159 | fn main() { | ||
1160 | <test_mod::TestStruct::<()> as test_mod::TestTrait>::TEST_CONST | ||
1161 | } | ||
1162 | ", | ||
1163 | ); | ||
1164 | } | ||
1165 | |||
1166 | #[test] | ||
1167 | fn trait_method_generic() { | ||
1168 | check_assist( | ||
1169 | qualify_path, | ||
1170 | r" | ||
1171 | mod test_mod { | ||
1172 | pub trait TestTrait { | ||
1173 | fn test_method<T>(&self); | ||
1174 | } | ||
1175 | pub struct TestStruct {} | ||
1176 | impl TestTrait for TestStruct { | ||
1177 | fn test_method<T>(&self) {} | ||
1178 | } | ||
1179 | } | ||
1180 | |||
1181 | fn main() { | ||
1182 | let test_struct = test_mod::TestStruct {}; | ||
1183 | test_struct.test_meth<|>od::<()>() | ||
1184 | } | ||
1185 | ", | ||
1186 | r" | ||
1187 | mod test_mod { | ||
1188 | pub trait TestTrait { | ||
1189 | fn test_method<T>(&self); | ||
1190 | } | ||
1191 | pub struct TestStruct {} | ||
1192 | impl TestTrait for TestStruct { | ||
1193 | fn test_method<T>(&self) {} | ||
1194 | } | ||
1195 | } | ||
1196 | |||
1197 | fn main() { | ||
1198 | let test_struct = test_mod::TestStruct {}; | ||
1199 | test_mod::TestTrait::test_method::<()>(&test_struct) | ||
1200 | } | ||
1201 | ", | ||
1202 | ); | ||
1203 | } | ||
1048 | } | 1204 | } |
diff --git a/crates/ide/src/inlay_hints.rs b/crates/ide/src/inlay_hints.rs index adb93efd7..ac704ae21 100644 --- a/crates/ide/src/inlay_hints.rs +++ b/crates/ide/src/inlay_hints.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | use assists::utils::FamousDefs; | 1 | use assists::utils::FamousDefs; |
2 | use either::Either; | 2 | use either::Either; |
3 | use hir::{known, HirDisplay, Semantics}; | 3 | use hir::{known, Callable, HirDisplay, Semantics}; |
4 | use ide_db::RootDatabase; | 4 | use ide_db::RootDatabase; |
5 | use stdx::to_lower_snake_case; | 5 | use stdx::to_lower_snake_case; |
6 | use syntax::{ | 6 | use syntax::{ |
@@ -170,7 +170,7 @@ fn get_param_name_hints( | |||
170 | }; | 170 | }; |
171 | Some((param_name, arg)) | 171 | Some((param_name, arg)) |
172 | }) | 172 | }) |
173 | .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, ¶m_name, &arg)) | 173 | .filter(|(param_name, arg)| should_show_param_name_hint(sema, &callable, param_name, &arg)) |
174 | .map(|(param_name, arg)| InlayHint { | 174 | .map(|(param_name, arg)| InlayHint { |
175 | range: arg.syntax().text_range(), | 175 | range: arg.syntax().text_range(), |
176 | kind: InlayKind::ParameterHint, | 176 | kind: InlayKind::ParameterHint, |
@@ -334,9 +334,11 @@ fn should_show_param_name_hint( | |||
334 | | hir::CallableKind::TupleEnumVariant(_) | 334 | | hir::CallableKind::TupleEnumVariant(_) |
335 | | hir::CallableKind::Closure => None, | 335 | | hir::CallableKind::Closure => None, |
336 | }; | 336 | }; |
337 | |||
337 | if param_name.is_empty() | 338 | if param_name.is_empty() |
338 | || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_')) | 339 | || Some(param_name) == fn_name.as_ref().map(|s| s.trim_start_matches('_')) |
339 | || is_argument_similar_to_param_name(sema, argument, param_name) | 340 | || is_argument_similar_to_param_name(sema, argument, param_name) |
341 | || is_param_name_similar_to_fn_name(param_name, callable, fn_name.as_ref()) | ||
340 | || param_name.starts_with("ra_fixture") | 342 | || param_name.starts_with("ra_fixture") |
341 | { | 343 | { |
342 | return false; | 344 | return false; |
@@ -364,6 +366,26 @@ fn is_argument_similar_to_param_name( | |||
364 | } | 366 | } |
365 | } | 367 | } |
366 | 368 | ||
369 | fn is_param_name_similar_to_fn_name( | ||
370 | param_name: &str, | ||
371 | callable: &Callable, | ||
372 | fn_name: Option<&String>, | ||
373 | ) -> bool { | ||
374 | // if it's the only parameter, don't show it if: | ||
375 | // - is the same as the function name, or | ||
376 | // - the function ends with '_' + param_name | ||
377 | |||
378 | match (callable.n_params(), fn_name) { | ||
379 | (1, Some(function)) => { | ||
380 | function == param_name | ||
381 | || (function.len() > param_name.len() | ||
382 | && function.ends_with(param_name) | ||
383 | && function[..function.len() - param_name.len()].ends_with('_')) | ||
384 | } | ||
385 | _ => false, | ||
386 | } | ||
387 | } | ||
388 | |||
367 | fn is_enum_name_similar_to_param_name( | 389 | fn is_enum_name_similar_to_param_name( |
368 | sema: &Semantics<RootDatabase>, | 390 | sema: &Semantics<RootDatabase>, |
369 | argument: &ast::Expr, | 391 | argument: &ast::Expr, |
@@ -457,6 +479,88 @@ fn main() { | |||
457 | } | 479 | } |
458 | 480 | ||
459 | #[test] | 481 | #[test] |
482 | fn param_name_similar_to_fn_name_still_hints() { | ||
483 | check_with_config( | ||
484 | InlayHintsConfig { | ||
485 | parameter_hints: true, | ||
486 | type_hints: false, | ||
487 | chaining_hints: false, | ||
488 | max_length: None, | ||
489 | }, | ||
490 | r#" | ||
491 | fn max(x: i32, y: i32) -> i32 { x + y } | ||
492 | fn main() { | ||
493 | let _x = max( | ||
494 | 4, | ||
495 | //^ x | ||
496 | 4, | ||
497 | //^ y | ||
498 | ); | ||
499 | }"#, | ||
500 | ); | ||
501 | } | ||
502 | |||
503 | #[test] | ||
504 | fn param_name_similar_to_fn_name() { | ||
505 | check_with_config( | ||
506 | InlayHintsConfig { | ||
507 | parameter_hints: true, | ||
508 | type_hints: false, | ||
509 | chaining_hints: false, | ||
510 | max_length: None, | ||
511 | }, | ||
512 | r#" | ||
513 | fn param_with_underscore(with_underscore: i32) -> i32 { with_underscore } | ||
514 | fn main() { | ||
515 | let _x = param_with_underscore( | ||
516 | 4, | ||
517 | ); | ||
518 | }"#, | ||
519 | ); | ||
520 | } | ||
521 | |||
522 | #[test] | ||
523 | fn param_name_same_as_fn_name() { | ||
524 | check_with_config( | ||
525 | InlayHintsConfig { | ||
526 | parameter_hints: true, | ||
527 | type_hints: false, | ||
528 | chaining_hints: false, | ||
529 | max_length: None, | ||
530 | }, | ||
531 | r#" | ||
532 | fn foo(foo: i32) -> i32 { foo } | ||
533 | fn main() { | ||
534 | let _x = foo( | ||
535 | 4, | ||
536 | ); | ||
537 | }"#, | ||
538 | ); | ||
539 | } | ||
540 | |||
541 | #[test] | ||
542 | fn never_hide_param_when_multiple_params() { | ||
543 | check_with_config( | ||
544 | InlayHintsConfig { | ||
545 | parameter_hints: true, | ||
546 | type_hints: false, | ||
547 | chaining_hints: false, | ||
548 | max_length: None, | ||
549 | }, | ||
550 | r#" | ||
551 | fn foo(bar: i32, baz: i32) -> i32 { bar + baz } | ||
552 | fn main() { | ||
553 | let _x = foo( | ||
554 | 4, | ||
555 | //^ bar | ||
556 | 8, | ||
557 | //^ baz | ||
558 | ); | ||
559 | }"#, | ||
560 | ); | ||
561 | } | ||
562 | |||
563 | #[test] | ||
460 | fn hints_disabled() { | 564 | fn hints_disabled() { |
461 | check_with_config( | 565 | check_with_config( |
462 | InlayHintsConfig { | 566 | InlayHintsConfig { |