aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_assists/src/ast_editor.rs30
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/move_bounds.rs135
-rw-r--r--docs/user/features.md10
4 files changed, 177 insertions, 0 deletions
diff --git a/crates/ra_assists/src/ast_editor.rs b/crates/ra_assists/src/ast_editor.rs
index 048478662..a710edce8 100644
--- a/crates/ra_assists/src/ast_editor.rs
+++ b/crates/ra_assists/src/ast_editor.rs
@@ -297,6 +297,11 @@ impl AstBuilder<ast::Path> {
297 ast_node_from_file_text(text) 297 ast_node_from_file_text(text)
298 } 298 }
299 299
300 pub fn from_name(name: ast::Name) -> ast::Path {
301 let name = name.syntax().to_string();
302 Self::from_text(name.as_str())
303 }
304
300 pub fn from_pieces(enum_name: ast::Name, var_name: ast::Name) -> ast::Path { 305 pub fn from_pieces(enum_name: ast::Name, var_name: ast::Name) -> ast::Path {
301 Self::from_text(&format!("{}::{}", enum_name.syntax(), var_name.syntax())) 306 Self::from_text(&format!("{}::{}", enum_name.syntax(), var_name.syntax()))
302 } 307 }
@@ -380,6 +385,31 @@ impl AstBuilder<ast::MatchArmList> {
380 } 385 }
381} 386}
382 387
388impl AstBuilder<ast::WherePred> {
389 fn from_text(text: &str) -> ast::WherePred {
390 ast_node_from_file_text(&format!("fn f() where {} {{ }}", text))
391 }
392
393 pub fn from_pieces(
394 path: ast::Path,
395 bounds: impl Iterator<Item = ast::TypeBound>,
396 ) -> ast::WherePred {
397 let bounds = bounds.map(|b| b.syntax().to_string()).collect::<Vec<_>>().join(" + ");
398 Self::from_text(&format!("{}: {}", path.syntax(), bounds))
399 }
400}
401
402impl AstBuilder<ast::WhereClause> {
403 fn from_text(text: &str) -> ast::WhereClause {
404 ast_node_from_file_text(&format!("fn f() where {} {{ }}", text))
405 }
406
407 pub fn from_predicates(preds: impl Iterator<Item = ast::WherePred>) -> ast::WhereClause {
408 let preds = preds.map(|p| p.syntax().to_string()).collect::<Vec<_>>().join(", ");
409 Self::from_text(preds.as_str())
410 }
411}
412
383fn ast_node_from_file_text<N: AstNode>(text: &str) -> N { 413fn ast_node_from_file_text<N: AstNode>(text: &str) -> N {
384 let parse = SourceFile::parse(text); 414 let parse = SourceFile::parse(text);
385 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); 415 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap();
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs
index 03eec73ad..10ccc345c 100644
--- a/crates/ra_assists/src/lib.rs
+++ b/crates/ra_assists/src/lib.rs
@@ -102,6 +102,7 @@ mod remove_dbg;
102pub mod auto_import; 102pub mod auto_import;
103mod add_missing_impl_members; 103mod add_missing_impl_members;
104mod move_guard; 104mod move_guard;
105mod move_bounds;
105 106
106fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 107fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] {
107 &[ 108 &[
@@ -123,6 +124,7 @@ fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assis
123 inline_local_variable::inline_local_varialbe, 124 inline_local_variable::inline_local_varialbe,
124 move_guard::move_guard_to_arm_body, 125 move_guard::move_guard_to_arm_body,
125 move_guard::move_arm_cond_to_match_guard, 126 move_guard::move_arm_cond_to_match_guard,
127 move_bounds::move_bounds_to_where_clause,
126 ] 128 ]
127} 129}
128 130
diff --git a/crates/ra_assists/src/move_bounds.rs b/crates/ra_assists/src/move_bounds.rs
new file mode 100644
index 000000000..526de1d98
--- /dev/null
+++ b/crates/ra_assists/src/move_bounds.rs
@@ -0,0 +1,135 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement,
5 SyntaxKind::*,
6 TextRange,
7};
8
9use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId};
10
11pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?;
13
14 let mut type_params = type_param_list.type_params();
15 if type_params.all(|p| p.type_bound_list().is_none()) {
16 return None;
17 }
18
19 let parent = type_param_list.syntax().parent()?;
20 if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() {
21 return None;
22 }
23
24 let anchor: SyntaxElement = match parent.kind() {
25 FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(),
26 TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(),
27 IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(),
28 ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(),
29 STRUCT_DEF => parent
30 .children_with_tokens()
31 .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?,
32 _ => return None,
33 };
34
35 ctx.add_action(
36 AssistId("move_bounds_to_where_clause"),
37 "move_bounds_to_where_clause",
38 |edit| {
39 let type_params = type_param_list.type_params().collect::<Vec<_>>();
40
41 for param in &type_params {
42 if let Some(bounds) = param.type_bound_list() {
43 let colon = param
44 .syntax()
45 .children_with_tokens()
46 .find(|it| it.kind() == COLON)
47 .unwrap();
48 let start = colon.text_range().start();
49 let end = bounds.syntax().text_range().end();
50 edit.delete(TextRange::from_to(start, end));
51 }
52 }
53
54 let predicates = type_params.iter().filter_map(build_predicate);
55 let where_clause = AstBuilder::<ast::WhereClause>::from_predicates(predicates);
56
57 let to_insert = match anchor.prev_sibling_or_token() {
58 Some(ref elem) if elem.kind() == WHITESPACE => {
59 format!("{} ", where_clause.syntax())
60 }
61 _ => format!(" {}", where_clause.syntax()),
62 };
63 edit.insert(anchor.text_range().start(), to_insert);
64 edit.target(type_param_list.syntax().text_range());
65 },
66 );
67
68 ctx.build()
69}
70
71fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> {
72 let path = AstBuilder::<ast::Path>::from_name(param.name()?);
73 let predicate =
74 AstBuilder::<ast::WherePred>::from_pieces(path, param.type_bound_list()?.bounds());
75 Some(predicate)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 use crate::helpers::check_assist;
83
84 #[test]
85 fn move_bounds_to_where_clause_fn() {
86 check_assist(
87 move_bounds_to_where_clause,
88 r#"
89 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
90 "#,
91 r#"
92 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {}
93 "#,
94 );
95 }
96
97 #[test]
98 fn move_bounds_to_where_clause_impl() {
99 check_assist(
100 move_bounds_to_where_clause,
101 r#"
102 impl<U: u32, <|>T> A<U, T> {}
103 "#,
104 r#"
105 impl<U, <|>T> A<U, T> where U: u32 {}
106 "#,
107 );
108 }
109
110 #[test]
111 fn move_bounds_to_where_clause_struct() {
112 check_assist(
113 move_bounds_to_where_clause,
114 r#"
115 struct A<<|>T: Iterator<Item = u32>> {}
116 "#,
117 r#"
118 struct A<<|>T> where T: Iterator<Item = u32> {}
119 "#,
120 );
121 }
122
123 #[test]
124 fn move_bounds_to_where_clause_tuple_struct() {
125 check_assist(
126 move_bounds_to_where_clause,
127 r#"
128 struct Pair<<|>T: u32>(T, T);
129 "#,
130 r#"
131 struct Pair<<|>T>(T, T) where T: u32;
132 "#,
133 );
134 }
135}
diff --git a/docs/user/features.md b/docs/user/features.md
index b6e6008c4..1034a5117 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -435,6 +435,16 @@ fn f() {
435} 435}
436``` 436```
437 437
438- Move type bounds to where clause
439
440```rust
441// before:
442fn foo<T: u32, F: FnOnce(T) -> T>() {}
443
444// after:
445fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
446```
447
438### Magic Completions 448### Magic Completions
439 449
440In addition to usual reference completion, rust-analyzer provides some ✨magic✨ 450In addition to usual reference completion, rust-analyzer provides some ✨magic✨