aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-11-13 08:59:48 +0000
committerGitHub <[email protected]>2019-11-13 08:59:48 +0000
commitd523366299c8d4813e9845c9402b8dd7b779856a (patch)
tree1e42b170b230271a5a906f0e9c19cbec27429929 /crates
parent6ca0d79cff8746ea4f0c8fb8645d14b8b81bc7fc (diff)
parent4cea6bb6f11e28a2d1d2e023d46caa82b0f38796 (diff)
Merge #2226
2226: Use strongly-typed ast building for early-return assist r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assists/early_return.rs128
-rw-r--r--crates/ra_syntax/src/ast/edit.rs2
-rw-r--r--crates/ra_syntax/src/ast/make.rs71
3 files changed, 131 insertions, 70 deletions
diff --git a/crates/ra_assists/src/assists/early_return.rs b/crates/ra_assists/src/assists/early_return.rs
index 570a07a20..264412526 100644
--- a/crates/ra_assists/src/assists/early_return.rs
+++ b/crates/ra_assists/src/assists/early_return.rs
@@ -1,4 +1,4 @@
1use std::ops::RangeInclusive; 1use std::{iter::once, ops::RangeInclusive};
2 2
3use hir::db::HirDatabase; 3use hir::db::HirDatabase;
4use ra_syntax::{ 4use ra_syntax::{
@@ -38,27 +38,30 @@ use crate::{
38// ``` 38// ```
39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> { 39pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?; 40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 if if_expr.else_branch().is_some() {
42 return None;
43 }
44
41 let cond = if_expr.condition()?; 45 let cond = if_expr.condition()?;
42 let mut if_let_ident: Option<String> = None;
43 46
44 // Check if there is an IfLet that we can handle. 47 // Check if there is an IfLet that we can handle.
45 match cond.pat() { 48 let if_let_pat = match cond.pat() {
46 None => {} // No IfLet, supported. 49 None => None, // No IfLet, supported.
47 Some(TupleStructPat(ref pat)) if pat.args().count() == 1usize => match &pat.path() { 50 Some(TupleStructPat(pat)) if pat.args().count() == 1 => {
48 Some(p) => match p.qualifier() { 51 let path = pat.path()?;
49 None => if_let_ident = Some(p.syntax().text().to_string()), 52 match path.qualifier() {
50 _ => return None, 53 None => {
51 }, 54 let bound_ident = pat.args().next().unwrap();
52 _ => return None, 55 Some((path, bound_ident))
53 }, 56 }
54 _ => return None, // Unsupported IfLet. 57 Some(_) => return None,
58 }
59 }
60 Some(_) => return None, // Unsupported IfLet.
55 }; 61 };
56 62
57 let expr = cond.expr()?; 63 let cond_expr = cond.expr()?;
58 let then_block = if_expr.then_branch()?.block()?; 64 let then_block = if_expr.then_branch()?.block()?;
59 if if_expr.else_branch().is_some() {
60 return None;
61 }
62 65
63 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?; 66 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?;
64 67
@@ -79,11 +82,11 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
79 82
80 let parent_container = parent_block.syntax().parent()?.parent()?; 83 let parent_container = parent_block.syntax().parent()?.parent()?;
81 84
82 let early_expression = match parent_container.kind() { 85 let early_expression: ast::Expr = match parent_container.kind() {
83 WHILE_EXPR | LOOP_EXPR => Some("continue"), 86 WHILE_EXPR | LOOP_EXPR => make::expr_continue().into(),
84 FN_DEF => Some("return"), 87 FN_DEF => make::expr_return().into(),
85 _ => None, 88 _ => return None,
86 }?; 89 };
87 90
88 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() { 91 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
89 return None; 92 return None;
@@ -94,22 +97,43 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
94 97
95 ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| { 98 ctx.add_assist(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| {
96 let if_indent_level = IndentLevel::from_node(&if_expr.syntax()); 99 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
97 let new_block = match if_let_ident { 100 let new_block = match if_let_pat {
98 None => { 101 None => {
99 // If. 102 // If.
100 let early_expression = &(early_expression.to_owned() + ";"); 103 let early_expression = &(early_expression.syntax().to_string() + ";");
101 let new_expr = 104 let new_expr = if_indent_level
102 if_indent_level.increase_indent(make::if_expression(&expr, early_expression)); 105 .increase_indent(make::if_expression(&cond_expr, early_expression));
103 replace(new_expr, &then_block, &parent_block, &if_expr) 106 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
104 } 107 }
105 Some(if_let_ident) => { 108 Some((path, bound_ident)) => {
106 // If-let. 109 // If-let.
107 let new_expr = if_indent_level.increase_indent(make::let_match_early( 110 let match_expr = {
108 expr, 111 let happy_arm = make::match_arm(
109 &if_let_ident, 112 once(
110 early_expression, 113 make::tuple_struct_pat(
111 )); 114 path,
112 replace(new_expr, &then_block, &parent_block, &if_expr) 115 once(make::bind_pat(make::name("it")).into()),
116 )
117 .into(),
118 ),
119 make::expr_path(make::path_from_name_ref(make::name_ref("it"))).into(),
120 );
121
122 let sad_arm = make::match_arm(
123 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
124 once(make::placeholder_pat().into()),
125 early_expression.into(),
126 );
127
128 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
129 };
130
131 let let_stmt = make::let_stmt(
132 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
133 Some(match_expr.into()),
134 );
135 let let_stmt = if_indent_level.increase_indent(let_stmt);
136 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
113 } 137 }
114 }; 138 };
115 edit.target(if_expr.syntax().text_range()); 139 edit.target(if_expr.syntax().text_range());
@@ -117,7 +141,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
117 edit.set_cursor(cursor_position); 141 edit.set_cursor(cursor_position);
118 142
119 fn replace( 143 fn replace(
120 new_expr: impl AstNode, 144 new_expr: &SyntaxNode,
121 then_block: &Block, 145 then_block: &Block,
122 parent_block: &Block, 146 parent_block: &Block,
123 if_expr: &ast::IfExpr, 147 if_expr: &ast::IfExpr,
@@ -130,7 +154,7 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
130 } else { 154 } else {
131 end_of_then 155 end_of_then
132 }; 156 };
133 let mut then_statements = new_expr.syntax().children_with_tokens().chain( 157 let mut then_statements = new_expr.children_with_tokens().chain(
134 then_block_items 158 then_block_items
135 .syntax() 159 .syntax()
136 .children_with_tokens() 160 .children_with_tokens()
@@ -151,9 +175,10 @@ pub(crate) fn convert_to_guarded_return(ctx: AssistCtx<impl HirDatabase>) -> Opt
151 175
152#[cfg(test)] 176#[cfg(test)]
153mod tests { 177mod tests {
154 use super::*;
155 use crate::helpers::{check_assist, check_assist_not_applicable}; 178 use crate::helpers::{check_assist, check_assist_not_applicable};
156 179
180 use super::*;
181
157 #[test] 182 #[test]
158 fn convert_inside_fn() { 183 fn convert_inside_fn() {
159 check_assist( 184 check_assist(
@@ -204,7 +229,7 @@ mod tests {
204 bar(); 229 bar();
205 le<|>t n = match n { 230 le<|>t n = match n {
206 Some(it) => it, 231 Some(it) => it,
207 None => return, 232 _ => return,
208 }; 233 };
209 foo(n); 234 foo(n);
210 235
@@ -216,6 +241,29 @@ mod tests {
216 } 241 }
217 242
218 #[test] 243 #[test]
244 fn convert_if_let_result() {
245 check_assist(
246 convert_to_guarded_return,
247 r#"
248 fn main() {
249 if<|> let Ok(x) = Err(92) {
250 foo(x);
251 }
252 }
253 "#,
254 r#"
255 fn main() {
256 le<|>t x = match Err(92) {
257 Ok(it) => it,
258 _ => return,
259 };
260 foo(x);
261 }
262 "#,
263 );
264 }
265
266 #[test]
219 fn convert_let_ok_inside_fn() { 267 fn convert_let_ok_inside_fn() {
220 check_assist( 268 check_assist(
221 convert_to_guarded_return, 269 convert_to_guarded_return,
@@ -235,7 +283,7 @@ mod tests {
235 bar(); 283 bar();
236 le<|>t n = match n { 284 le<|>t n = match n {
237 Ok(it) => it, 285 Ok(it) => it,
238 None => return, 286 _ => return,
239 }; 287 };
240 foo(n); 288 foo(n);
241 289
@@ -293,7 +341,7 @@ mod tests {
293 while true { 341 while true {
294 le<|>t n = match n { 342 le<|>t n = match n {
295 Some(it) => it, 343 Some(it) => it,
296 None => continue, 344 _ => continue,
297 }; 345 };
298 foo(n); 346 foo(n);
299 bar(); 347 bar();
@@ -350,7 +398,7 @@ mod tests {
350 loop { 398 loop {
351 le<|>t n = match n { 399 le<|>t n = match n {
352 Some(it) => it, 400 Some(it) => it,
353 None => continue, 401 _ => continue,
354 }; 402 };
355 foo(n); 403 foo(n);
356 bar(); 404 bar();
diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs
index 47bdbb81a..6f005a2d8 100644
--- a/crates/ra_syntax/src/ast/edit.rs
+++ b/crates/ra_syntax/src/ast/edit.rs
@@ -358,7 +358,7 @@ fn replace_children<N: AstNode>(
358fn test_increase_indent() { 358fn test_increase_indent() {
359 let arm_list = { 359 let arm_list = {
360 let arm = make::match_arm(iter::once(make::placeholder_pat().into()), make::expr_unit()); 360 let arm = make::match_arm(iter::once(make::placeholder_pat().into()), make::expr_unit());
361 make::match_arm_list(vec![arm.clone(), arm].into_iter()) 361 make::match_arm_list(vec![arm.clone(), arm])
362 }; 362 };
363 assert_eq!( 363 assert_eq!(
364 arm_list.syntax().to_string(), 364 arm_list.syntax().to_string(),
diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs
index 95062ef6c..9749327fa 100644
--- a/crates/ra_syntax/src/ast/make.rs
+++ b/crates/ra_syntax/src/ast/make.rs
@@ -4,6 +4,10 @@ use itertools::Itertools;
4 4
5use crate::{ast, AstNode, SourceFile}; 5use crate::{ast, AstNode, SourceFile};
6 6
7pub fn name(text: &str) -> ast::Name {
8 ast_from_text(&format!("mod {};", text))
9}
10
7pub fn name_ref(text: &str) -> ast::NameRef { 11pub fn name_ref(text: &str) -> ast::NameRef {
8 ast_from_text(&format!("fn f() {{ {}; }}", text)) 12 ast_from_text(&format!("fn f() {{ {}; }}", text))
9} 13}
@@ -43,6 +47,21 @@ pub fn expr_unit() -> ast::Expr {
43pub fn expr_unimplemented() -> ast::Expr { 47pub fn expr_unimplemented() -> ast::Expr {
44 expr_from_text("unimplemented!()") 48 expr_from_text("unimplemented!()")
45} 49}
50pub fn expr_path(path: ast::Path) -> ast::Expr {
51 expr_from_text(&path.syntax().to_string())
52}
53pub fn expr_continue() -> ast::Expr {
54 expr_from_text("continue")
55}
56pub fn expr_break() -> ast::Expr {
57 expr_from_text("break")
58}
59pub fn expr_return() -> ast::Expr {
60 expr_from_text("return")
61}
62pub fn expr_match(expr: ast::Expr, match_arm_list: ast::MatchArmList) -> ast::Expr {
63 expr_from_text(&format!("match {} {}", expr.syntax(), match_arm_list.syntax()))
64}
46fn expr_from_text(text: &str) -> ast::Expr { 65fn expr_from_text(text: &str) -> ast::Expr {
47 ast_from_text(&format!("const C: () = {};", text)) 66 ast_from_text(&format!("const C: () = {};", text))
48} 67}
@@ -65,9 +84,9 @@ pub fn placeholder_pat() -> ast::PlaceholderPat {
65 84
66pub fn tuple_struct_pat( 85pub fn tuple_struct_pat(
67 path: ast::Path, 86 path: ast::Path,
68 pats: impl Iterator<Item = ast::Pat>, 87 pats: impl IntoIterator<Item = ast::Pat>,
69) -> ast::TupleStructPat { 88) -> ast::TupleStructPat {
70 let pats_str = pats.map(|p| p.syntax().to_string()).join(", "); 89 let pats_str = pats.into_iter().map(|p| p.syntax().to_string()).join(", ");
71 return from_text(&format!("{}({})", path.syntax(), pats_str)); 90 return from_text(&format!("{}({})", path.syntax(), pats_str));
72 91
73 fn from_text(text: &str) -> ast::TupleStructPat { 92 fn from_text(text: &str) -> ast::TupleStructPat {
@@ -75,8 +94,8 @@ pub fn tuple_struct_pat(
75 } 94 }
76} 95}
77 96
78pub fn record_pat(path: ast::Path, pats: impl Iterator<Item = ast::Pat>) -> ast::RecordPat { 97pub fn record_pat(path: ast::Path, pats: impl IntoIterator<Item = ast::Pat>) -> ast::RecordPat {
79 let pats_str = pats.map(|p| p.syntax().to_string()).join(", "); 98 let pats_str = pats.into_iter().map(|p| p.syntax().to_string()).join(", ");
80 return from_text(&format!("{} {{ {} }}", path.syntax(), pats_str)); 99 return from_text(&format!("{} {{ {} }}", path.syntax(), pats_str));
81 100
82 fn from_text(text: &str) -> ast::RecordPat { 101 fn from_text(text: &str) -> ast::RecordPat {
@@ -92,8 +111,8 @@ pub fn path_pat(path: ast::Path) -> ast::PathPat {
92 } 111 }
93} 112}
94 113
95pub fn match_arm(pats: impl Iterator<Item = ast::Pat>, expr: ast::Expr) -> ast::MatchArm { 114pub fn match_arm(pats: impl IntoIterator<Item = ast::Pat>, expr: ast::Expr) -> ast::MatchArm {
96 let pats_str = pats.map(|p| p.syntax().to_string()).join(" | "); 115 let pats_str = pats.into_iter().map(|p| p.syntax().to_string()).join(" | ");
97 return from_text(&format!("{} => {}", pats_str, expr.syntax())); 116 return from_text(&format!("{} => {}", pats_str, expr.syntax()));
98 117
99 fn from_text(text: &str) -> ast::MatchArm { 118 fn from_text(text: &str) -> ast::MatchArm {
@@ -101,8 +120,8 @@ pub fn match_arm(pats: impl Iterator<Item = ast::Pat>, expr: ast::Expr) -> ast::
101 } 120 }
102} 121}
103 122
104pub fn match_arm_list(arms: impl Iterator<Item = ast::MatchArm>) -> ast::MatchArmList { 123pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::MatchArmList {
105 let arms_str = arms.map(|arm| format!("\n {}", arm.syntax())).join(","); 124 let arms_str = arms.into_iter().map(|arm| format!("\n {}", arm.syntax())).join(",");
106 return from_text(&format!("{},\n", arms_str)); 125 return from_text(&format!("{},\n", arms_str));
107 126
108 fn from_text(text: &str) -> ast::MatchArmList { 127 fn from_text(text: &str) -> ast::MatchArmList {
@@ -110,25 +129,11 @@ pub fn match_arm_list(arms: impl Iterator<Item = ast::MatchArm>) -> ast::MatchAr
110 } 129 }
111} 130}
112 131
113pub fn let_match_early(expr: ast::Expr, path: &str, early_expression: &str) -> ast::LetStmt { 132pub fn where_pred(
114 return from_text(&format!( 133 path: ast::Path,
115 r#"let {} = match {} {{ 134 bounds: impl IntoIterator<Item = ast::TypeBound>,
116 {}(it) => it, 135) -> ast::WherePred {
117 None => {}, 136 let bounds = bounds.into_iter().map(|b| b.syntax().to_string()).join(" + ");
118}};"#,
119 expr.syntax().text(),
120 expr.syntax().text(),
121 path,
122 early_expression
123 ));
124
125 fn from_text(text: &str) -> ast::LetStmt {
126 ast_from_text(&format!("fn f() {{ {} }}", text))
127 }
128}
129
130pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>) -> ast::WherePred {
131 let bounds = bounds.map(|b| b.syntax().to_string()).join(" + ");
132 return from_text(&format!("{}: {}", path.syntax(), bounds)); 137 return from_text(&format!("{}: {}", path.syntax(), bounds));
133 138
134 fn from_text(text: &str) -> ast::WherePred { 139 fn from_text(text: &str) -> ast::WherePred {
@@ -136,8 +141,8 @@ pub fn where_pred(path: ast::Path, bounds: impl Iterator<Item = ast::TypeBound>)
136 } 141 }
137} 142}
138 143
139pub fn where_clause(preds: impl Iterator<Item = ast::WherePred>) -> ast::WhereClause { 144pub fn where_clause(preds: impl IntoIterator<Item = ast::WherePred>) -> ast::WhereClause {
140 let preds = preds.map(|p| p.syntax().to_string()).join(", "); 145 let preds = preds.into_iter().map(|p| p.syntax().to_string()).join(", ");
141 return from_text(preds.as_str()); 146 return from_text(preds.as_str());
142 147
143 fn from_text(text: &str) -> ast::WhereClause { 148 fn from_text(text: &str) -> ast::WhereClause {
@@ -153,6 +158,14 @@ pub fn if_expression(condition: &ast::Expr, statement: &str) -> ast::IfExpr {
153 )) 158 ))
154} 159}
155 160
161pub fn let_stmt(pattern: ast::Pat, initializer: Option<ast::Expr>) -> ast::LetStmt {
162 let text = match initializer {
163 Some(it) => format!("let {} = {};", pattern.syntax(), it.syntax()),
164 None => format!("let {};", pattern.syntax()),
165 };
166 ast_from_text(&format!("fn f() {{ {} }}", text))
167}
168
156fn ast_from_text<N: AstNode>(text: &str) -> N { 169fn ast_from_text<N: AstNode>(text: &str) -> N {
157 let parse = SourceFile::parse(text); 170 let parse = SourceFile::parse(text);
158 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap(); 171 let res = parse.tree().syntax().descendants().find_map(N::cast).unwrap();