diff options
author | Ekaterina Babshukova <[email protected]> | 2019-09-04 17:48:45 +0100 |
---|---|---|
committer | Ekaterina Babshukova <[email protected]> | 2019-09-05 11:29:13 +0100 |
commit | acb89d2be12f6a1556d9a366231604371a62fdcd (patch) | |
tree | c151ac6fd66c7524f2a9c21482f53cfee20d828d | |
parent | 36d7b75c957d4ebf8e8f75ca59c79866cc702df4 (diff) |
add assist to move type bounds to where clause
-rw-r--r-- | crates/ra_assists/src/ast_editor.rs | 30 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_assists/src/move_bounds.rs | 135 | ||||
-rw-r--r-- | docs/user/features.md | 10 |
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 | ||
388 | impl 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 | |||
402 | impl 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 | |||
383 | fn ast_node_from_file_text<N: AstNode>(text: &str) -> N { | 413 | fn 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; | |||
102 | pub mod auto_import; | 102 | pub mod auto_import; |
103 | mod add_missing_impl_members; | 103 | mod add_missing_impl_members; |
104 | mod move_guard; | 104 | mod move_guard; |
105 | mod move_bounds; | ||
105 | 106 | ||
106 | fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { | 107 | fn 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 @@ | |||
1 | use hir::db::HirDatabase; | ||
2 | use ra_syntax::{ | ||
3 | ast::{self, AstNode, NameOwner, TypeBoundsOwner}, | ||
4 | SyntaxElement, | ||
5 | SyntaxKind::*, | ||
6 | TextRange, | ||
7 | }; | ||
8 | |||
9 | use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId}; | ||
10 | |||
11 | pub(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 | |||
71 | fn 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)] | ||
79 | mod 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: | ||
442 | fn foo<T: u32, F: FnOnce(T) -> T>() {} | ||
443 | |||
444 | // after: | ||
445 | fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {} | ||
446 | ``` | ||
447 | |||
438 | ### Magic Completions | 448 | ### Magic Completions |
439 | 449 | ||
440 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ | 450 | In addition to usual reference completion, rust-analyzer provides some ✨magic✨ |