Skip to content

Commit 9edaf0c

Browse files
committed
extract_function is const aware
1 parent e62ce6f commit 9edaf0c

File tree

1 file changed

+154
-75
lines changed

1 file changed

+154
-75
lines changed

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 154 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -71,35 +71,9 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
7171
syntax::NodeOrToken::Token(t) => t.parent()?,
7272
};
7373
let body = extraction_target(&node, range)?;
74-
let container_expr = body.parent()?.ancestors().find_map(|it| {
75-
// double Option as we want to short circuit
76-
let res = match_ast! {
77-
match it {
78-
ast::ClosureExpr(closure) => closure.body(),
79-
ast::EffectExpr(effect) => effect.block_expr().map(ast::Expr::BlockExpr),
80-
ast::Fn(fn_) => fn_.body().map(ast::Expr::BlockExpr),
81-
ast::Static(statik) => statik.body(),
82-
ast::ConstArg(ca) => ca.expr(),
83-
ast::Const(konst) => konst.body(),
84-
ast::ConstParam(cp) => cp.default_val(),
85-
ast::ConstBlockPat(cbp) => cbp.block_expr().map(ast::Expr::BlockExpr),
86-
ast::Variant(__) => None,
87-
ast::Meta(__) => None,
88-
_ => return None,
89-
}
90-
};
91-
Some(res)
92-
})??;
93-
let container_tail = match container_expr {
94-
ast::Expr::BlockExpr(block) => block.tail_expr(),
95-
expr => Some(expr),
96-
};
97-
let in_tail =
98-
container_tail.zip(body.tail_expr()).map_or(false, |(container_tail, body_tail)| {
99-
container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
100-
});
74+
let mods = body.analyze_container()?;
10175

102-
let (locals_used, has_await, self_param) = body.analyze(&ctx.sema);
76+
let (locals_used, self_param) = body.analyze(&ctx.sema);
10377

10478
let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding };
10579
let insert_after = node_to_insert_after(&body, anchor)?;
@@ -125,10 +99,6 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
12599

126100
let params = body.extracted_function_params(ctx, locals_used.iter().copied());
127101

128-
let insert_comma = body
129-
.parent()
130-
.and_then(ast::MatchArm::cast)
131-
.map_or(false, |it| it.comma_token().is_none());
132102
let fun = Function {
133103
name: "fun_name".to_string(),
134104
self_param,
@@ -137,18 +107,15 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
137107
ret_ty,
138108
body,
139109
outliving_locals,
110+
mods,
140111
};
141112

142113
let new_indent = IndentLevel::from_node(&insert_after);
143114
let old_indent = fun.body.indent_level();
144115

145-
builder.replace(target_range, make_call(ctx, &fun, old_indent, has_await));
146-
if insert_comma {
147-
builder.insert(target_range.end(), ",");
148-
}
116+
builder.replace(target_range, make_call(ctx, &fun, old_indent));
149117

150-
let fn_def =
151-
format_function(ctx, module, &fun, old_indent, new_indent, has_await, in_tail);
118+
let fn_def = format_function(ctx, module, &fun, old_indent, new_indent);
152119
let insert_offset = insert_after.text_range().end();
153120
match ctx.config.snippet_cap {
154121
Some(cap) => builder.insert_snippet(cap, insert_offset, fn_def),
@@ -210,6 +177,7 @@ struct Function {
210177
ret_ty: RetType,
211178
body: FunctionBody,
212179
outliving_locals: Vec<OutlivedLocal>,
180+
mods: Modifiers,
213181
}
214182

215183
#[derive(Debug)]
@@ -248,6 +216,13 @@ enum Anchor {
248216
#[derive(Debug)]
249217
struct ControlFlow {
250218
kind: Option<FlowKind>,
219+
is_async: bool,
220+
}
221+
222+
#[derive(Copy, Clone, Debug)]
223+
struct Modifiers {
224+
is_const: bool,
225+
is_in_tail: bool,
251226
}
252227

253228
/// Control flow that is exported from extracted function
@@ -605,13 +580,11 @@ impl FunctionBody {
605580
fn analyze(
606581
&self,
607582
sema: &Semantics<RootDatabase>,
608-
) -> (FxIndexSet<Local>, bool, Option<ast::SelfParam>) {
583+
) -> (FxIndexSet<Local>, Option<ast::SelfParam>) {
609584
// FIXME: currently usages inside macros are not found
610-
let mut has_await = false;
611585
let mut self_param = None;
612586
let mut res = FxIndexSet::default();
613587
self.walk_expr(&mut |expr| {
614-
has_await |= matches!(expr, ast::Expr::AwaitExpr(_));
615588
let name_ref = match expr {
616589
ast::Expr::PathExpr(path_expr) => {
617590
path_expr.path().and_then(|it| it.as_single_name_ref())
@@ -644,7 +617,60 @@ impl FunctionBody {
644617
}
645618
}
646619
});
647-
(res, has_await, self_param)
620+
(res, self_param)
621+
}
622+
623+
fn analyze_container(&self) -> Option<Modifiers> {
624+
let mut is_const = false;
625+
let container_expr = self.parent()?.ancestors().find_map(|it| {
626+
// double Option as we want to short circuit
627+
let res = match_ast! {
628+
match it {
629+
ast::ClosureExpr(closure) => closure.body(),
630+
ast::EffectExpr(effect) => {
631+
is_const = effect.const_token().is_some();
632+
effect.block_expr().map(ast::Expr::BlockExpr)
633+
},
634+
ast::Fn(fn_) => {
635+
is_const = fn_.const_token().is_some();
636+
fn_.body().map(ast::Expr::BlockExpr)
637+
},
638+
ast::Static(statik) => {
639+
is_const = true;
640+
statik.body()
641+
},
642+
ast::ConstArg(ca) => {
643+
is_const = true;
644+
ca.expr()
645+
},
646+
ast::Const(konst) => {
647+
is_const = true;
648+
konst.body()
649+
},
650+
ast::ConstParam(cp) => {
651+
is_const = true;
652+
cp.default_val()
653+
},
654+
ast::ConstBlockPat(cbp) => {
655+
is_const = true;
656+
cbp.block_expr().map(ast::Expr::BlockExpr)
657+
},
658+
ast::Variant(__) => None,
659+
ast::Meta(__) => None,
660+
_ => return None,
661+
}
662+
};
663+
Some(res)
664+
})??;
665+
let container_tail = match container_expr {
666+
ast::Expr::BlockExpr(block) => block.tail_expr(),
667+
expr => Some(expr),
668+
};
669+
let is_in_tail =
670+
container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| {
671+
container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
672+
});
673+
Some(Modifiers { is_in_tail, is_const })
648674
}
649675

650676
fn return_ty(&self, ctx: &AssistContext) -> Option<RetType> {
@@ -673,6 +699,7 @@ impl FunctionBody {
673699
let mut try_expr = None;
674700
let mut break_expr = None;
675701
let mut continue_expr = None;
702+
let mut is_async = false;
676703

677704
let mut loop_depth = 0;
678705

@@ -703,6 +730,7 @@ impl FunctionBody {
703730
ast::Expr::ContinueExpr(it) if loop_depth == 0 => {
704731
continue_expr = Some(it);
705732
}
733+
ast::Expr::AwaitExpr(_) => is_async = true,
706734
_ => {}
707735
}
708736
false
@@ -746,7 +774,7 @@ impl FunctionBody {
746774
(None, None, None, None) => None,
747775
};
748776

749-
Some(ControlFlow { kind })
777+
Some(ControlFlow { kind, is_async })
750778
}
751779
/// find variables that should be extracted as params
752780
///
@@ -1031,12 +1059,7 @@ fn node_to_insert_after(body: &FunctionBody, anchor: Anchor) -> Option<SyntaxNod
10311059
last_ancestor
10321060
}
10331061

1034-
fn make_call(
1035-
ctx: &AssistContext,
1036-
fun: &Function,
1037-
indent: IndentLevel,
1038-
body_contains_await: bool,
1039-
) -> String {
1062+
fn make_call(ctx: &AssistContext, fun: &Function, indent: IndentLevel) -> String {
10401063
let ret_ty = fun.return_type(ctx);
10411064

10421065
let args = fun.params.iter().map(|param| param.to_arg(ctx));
@@ -1053,6 +1076,8 @@ fn make_call(
10531076

10541077
let expr = handler.make_call_expr(call_expr).indent(indent);
10551078

1079+
let mut_modifier = |var: &OutlivedLocal| if var.mut_usage_outside_body { "mut " } else { "" };
1080+
10561081
let mut buf = String::new();
10571082
match fun.outliving_locals.as_slice() {
10581083
[] => {}
@@ -1068,18 +1093,18 @@ fn make_call(
10681093
buf.push_str(") = ");
10691094
}
10701095
}
1071-
fn mut_modifier(var: &OutlivedLocal) -> &'static str {
1072-
if var.mut_usage_outside_body {
1073-
"mut "
1074-
} else {
1075-
""
1076-
}
1077-
}
10781096
format_to!(buf, "{}", expr);
1079-
if body_contains_await {
1097+
if fun.control_flow.is_async {
10801098
buf.push_str(".await");
10811099
}
1082-
if fun.ret_ty.is_unit() && (!fun.outliving_locals.is_empty() || !expr.is_block_like()) {
1100+
let insert_comma = fun
1101+
.body
1102+
.parent()
1103+
.and_then(ast::MatchArm::cast)
1104+
.map_or(false, |it| it.comma_token().is_none());
1105+
if insert_comma {
1106+
buf.push(',');
1107+
} else if fun.ret_ty.is_unit() && (!fun.outliving_locals.is_empty() || !expr.is_block_like()) {
10831108
buf.push(';');
10841109
}
10851110
buf
@@ -1209,17 +1234,32 @@ fn format_function(
12091234
fun: &Function,
12101235
old_indent: IndentLevel,
12111236
new_indent: IndentLevel,
1212-
body_contains_await: bool,
1213-
in_tail: bool,
12141237
) -> String {
12151238
let mut fn_def = String::new();
12161239
let params = fun.make_param_list(ctx, module);
1217-
let ret_ty = fun.make_ret_ty(ctx, module, in_tail);
1218-
let body = make_body(ctx, old_indent, new_indent, fun, in_tail);
1219-
let async_kw = if body_contains_await { "async " } else { "" };
1240+
let ret_ty = fun.make_ret_ty(ctx, module);
1241+
let body = make_body(ctx, old_indent, new_indent, fun);
1242+
let const_kw = if fun.mods.is_const { "const " } else { "" };
1243+
let async_kw = if fun.control_flow.is_async { "async " } else { "" };
12201244
match ctx.config.snippet_cap {
1221-
Some(_) => format_to!(fn_def, "\n\n{}{}fn $0{}{}", new_indent, async_kw, fun.name, params),
1222-
None => format_to!(fn_def, "\n\n{}{}fn {}{}", new_indent, async_kw, fun.name, params),
1245+
Some(_) => format_to!(
1246+
fn_def,
1247+
"\n\n{}{}{}fn $0{}{}",
1248+
new_indent,
1249+
const_kw,
1250+
async_kw,
1251+
fun.name,
1252+
params
1253+
),
1254+
None => format_to!(
1255+
fn_def,
1256+
"\n\n{}{}{}fn {}{}",
1257+
new_indent,
1258+
const_kw,
1259+
async_kw,
1260+
fun.name,
1261+
params
1262+
),
12231263
}
12241264
if let Some(ret_ty) = ret_ty {
12251265
format_to!(fn_def, " {}", ret_ty);
@@ -1236,15 +1276,13 @@ impl Function {
12361276
make::param_list(self_param, params)
12371277
}
12381278

1239-
fn make_ret_ty(
1240-
&self,
1241-
ctx: &AssistContext,
1242-
module: hir::Module,
1243-
in_tail: bool,
1244-
) -> Option<ast::RetType> {
1279+
fn make_ret_ty(&self, ctx: &AssistContext, module: hir::Module) -> Option<ast::RetType> {
12451280
let fun_ty = self.return_type(ctx);
1246-
let handler =
1247-
if in_tail { FlowHandler::None } else { FlowHandler::from_ret_ty(self, &fun_ty) };
1281+
let handler = if self.mods.is_in_tail {
1282+
FlowHandler::None
1283+
} else {
1284+
FlowHandler::from_ret_ty(self, &fun_ty)
1285+
};
12481286
let ret_ty = match &handler {
12491287
FlowHandler::None => {
12501288
if matches!(fun_ty, FunType::Unit) {
@@ -1312,10 +1350,13 @@ fn make_body(
13121350
old_indent: IndentLevel,
13131351
new_indent: IndentLevel,
13141352
fun: &Function,
1315-
in_tail: bool,
13161353
) -> ast::BlockExpr {
13171354
let ret_ty = fun.return_type(ctx);
1318-
let handler = if in_tail { FlowHandler::None } else { FlowHandler::from_ret_ty(fun, &ret_ty) };
1355+
let handler = if fun.mods.is_in_tail {
1356+
FlowHandler::None
1357+
} else {
1358+
FlowHandler::from_ret_ty(fun, &ret_ty)
1359+
};
13191360
let block = match &fun.body {
13201361
FunctionBody::Expr(expr) => {
13211362
let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
@@ -3929,6 +3970,44 @@ fn $0fun_name() -> Result<(), i64> {
39293970
Result::<i32, i64>::Ok(0)?;
39303971
Ok(())
39313972
}
3973+
"#,
3974+
);
3975+
}
3976+
3977+
#[test]
3978+
fn extract_knows_const() {
3979+
check_assist(
3980+
extract_function,
3981+
r#"
3982+
const fn foo() {
3983+
$0()$0
3984+
}
3985+
"#,
3986+
r#"
3987+
const fn foo() {
3988+
fun_name();
3989+
}
3990+
3991+
const fn $0fun_name() {
3992+
()
3993+
}
3994+
"#,
3995+
);
3996+
check_assist(
3997+
extract_function,
3998+
r#"
3999+
const FOO: () = {
4000+
$0()$0
4001+
};
4002+
"#,
4003+
r#"
4004+
const FOO: () = {
4005+
fun_name();
4006+
};
4007+
4008+
const fn $0fun_name() {
4009+
()
4010+
}
39324011
"#,
39334012
);
39344013
}

0 commit comments

Comments
 (0)