mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-29 02:03:53 +00:00
Reorganize functions in extract_function assist
This commit is contained in:
parent
027a99fc70
commit
e62ce6f61a
@ -17,7 +17,7 @@ use syntax::{
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
AstNode,
|
||||
},
|
||||
ted,
|
||||
match_ast, ted,
|
||||
SyntaxKind::{self, COMMENT},
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, WalkEvent, T,
|
||||
};
|
||||
@ -71,17 +71,43 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
syntax::NodeOrToken::Token(t) => t.parent()?,
|
||||
};
|
||||
let body = extraction_target(&node, range)?;
|
||||
let container_expr = body.parent()?.ancestors().find_map(|it| {
|
||||
// double Option as we want to short circuit
|
||||
let res = match_ast! {
|
||||
match it {
|
||||
ast::ClosureExpr(closure) => closure.body(),
|
||||
ast::EffectExpr(effect) => effect.block_expr().map(ast::Expr::BlockExpr),
|
||||
ast::Fn(fn_) => fn_.body().map(ast::Expr::BlockExpr),
|
||||
ast::Static(statik) => statik.body(),
|
||||
ast::ConstArg(ca) => ca.expr(),
|
||||
ast::Const(konst) => konst.body(),
|
||||
ast::ConstParam(cp) => cp.default_val(),
|
||||
ast::ConstBlockPat(cbp) => cbp.block_expr().map(ast::Expr::BlockExpr),
|
||||
ast::Variant(__) => None,
|
||||
ast::Meta(__) => None,
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
Some(res)
|
||||
})??;
|
||||
let container_tail = match container_expr {
|
||||
ast::Expr::BlockExpr(block) => block.tail_expr(),
|
||||
expr => Some(expr),
|
||||
};
|
||||
let in_tail =
|
||||
container_tail.zip(body.tail_expr()).map_or(false, |(container_tail, body_tail)| {
|
||||
container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
|
||||
});
|
||||
|
||||
let (locals_used, has_await, self_param) = analyze_body(&ctx.sema, &body);
|
||||
let (locals_used, has_await, self_param) = body.analyze(&ctx.sema);
|
||||
|
||||
let anchor = if self_param.is_some() { Anchor::Method } else { Anchor::Freestanding };
|
||||
let insert_after = node_to_insert_after(&body, anchor)?;
|
||||
let module = ctx.sema.scope(&insert_after).module()?;
|
||||
|
||||
let ret_ty = body_return_ty(ctx, &body)?;
|
||||
let ret_values = ret_values(ctx, &body, node.parent().as_ref().unwrap_or(&node));
|
||||
|
||||
let control_flow = external_control_flow(ctx, &body)?;
|
||||
let ret_ty = body.return_ty(ctx)?;
|
||||
let control_flow = body.external_control_flow(ctx)?;
|
||||
let ret_values = body.ret_values(ctx, node.parent().as_ref().unwrap_or(&node));
|
||||
|
||||
let target_range = body.text_range();
|
||||
|
||||
@ -90,13 +116,14 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
"Extract into function",
|
||||
target_range,
|
||||
move |builder| {
|
||||
let ret_values: Vec<_> = ret_values.collect();
|
||||
if stdx::never!(!ret_values.is_empty() && !ret_ty.is_unit()) {
|
||||
let outliving_locals: Vec<_> = ret_values.collect();
|
||||
if stdx::never!(!outliving_locals.is_empty() && !ret_ty.is_unit()) {
|
||||
// We should not have variables that outlive body if we have expression block
|
||||
stdx::never!();
|
||||
return;
|
||||
}
|
||||
|
||||
let params = extracted_function_params(ctx, &body, locals_used.iter().copied());
|
||||
let params = body.extracted_function_params(ctx, locals_used.iter().copied());
|
||||
|
||||
let insert_comma = body
|
||||
.parent()
|
||||
@ -109,7 +136,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
control_flow,
|
||||
ret_ty,
|
||||
body,
|
||||
vars_defined_in_body_and_outlive: ret_values,
|
||||
outliving_locals,
|
||||
};
|
||||
|
||||
let new_indent = IndentLevel::from_node(&insert_after);
|
||||
@ -120,7 +147,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
builder.insert(target_range.end(), ",");
|
||||
}
|
||||
|
||||
let fn_def = format_function(ctx, module, &fun, old_indent, new_indent, has_await);
|
||||
let fn_def =
|
||||
format_function(ctx, module, &fun, old_indent, new_indent, has_await, in_tail);
|
||||
let insert_offset = insert_after.text_range().end();
|
||||
match ctx.config.snippet_cap {
|
||||
Some(cap) => builder.insert_snippet(cap, insert_offset, fn_def),
|
||||
@ -129,6 +157,50 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Try to guess what user wants to extract
|
||||
///
|
||||
/// We have basically have two cases:
|
||||
/// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs.
|
||||
/// Then we can use `ast::Expr`
|
||||
/// * We want a few statements for a block. E.g.
|
||||
/// ```rust,no_run
|
||||
/// fn foo() -> i32 {
|
||||
/// let m = 1;
|
||||
/// $0
|
||||
/// let n = 2;
|
||||
/// let k = 3;
|
||||
/// k + n
|
||||
/// $0
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> {
|
||||
if let Some(stmt) = ast::Stmt::cast(node.clone()) {
|
||||
return match stmt {
|
||||
ast::Stmt::Item(_) => None,
|
||||
ast::Stmt::ExprStmt(_) | ast::Stmt::LetStmt(_) => Some(FunctionBody::from_range(
|
||||
node.parent().and_then(ast::BlockExpr::cast)?,
|
||||
node.text_range(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
let expr = ast::Expr::cast(node.clone())?;
|
||||
// A node got selected fully
|
||||
if node.text_range() == selection_range {
|
||||
return FunctionBody::from_expr(expr.clone());
|
||||
}
|
||||
|
||||
// Covering element returned the parent block of one or multiple statements that have been selected
|
||||
if let ast::Expr::BlockExpr(block) = expr {
|
||||
// Extract the full statements.
|
||||
return Some(FunctionBody::from_range(block, selection_range));
|
||||
}
|
||||
|
||||
node.ancestors().find_map(ast::Expr::cast).and_then(FunctionBody::from_expr)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Function {
|
||||
name: String,
|
||||
@ -137,7 +209,7 @@ struct Function {
|
||||
control_flow: ControlFlow,
|
||||
ret_ty: RetType,
|
||||
body: FunctionBody,
|
||||
vars_defined_in_body_and_outlive: Vec<OutlivedLocal>,
|
||||
outliving_locals: Vec<OutlivedLocal>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -267,7 +339,7 @@ impl Function {
|
||||
match &self.ret_ty {
|
||||
RetType::Expr(ty) if ty.is_unit() => FunType::Unit,
|
||||
RetType::Expr(ty) => FunType::Single(ty.clone()),
|
||||
RetType::Stmt => match self.vars_defined_in_body_and_outlive.as_slice() {
|
||||
RetType::Stmt => match self.outliving_locals.as_slice() {
|
||||
[] => FunType::Unit,
|
||||
[var] => FunType::Single(var.local.ty(ctx.db())),
|
||||
vars => {
|
||||
@ -325,6 +397,24 @@ impl Param {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryKind {
|
||||
fn of_ty(ty: hir::Type, ctx: &AssistContext) -> Option<TryKind> {
|
||||
if ty.is_unknown() {
|
||||
// We favour Result for `expr?`
|
||||
return Some(TryKind::Result { ty });
|
||||
}
|
||||
let adt = ty.as_adt()?;
|
||||
let name = adt.name(ctx.db());
|
||||
// FIXME: use lang items to determine if it is std type or user defined
|
||||
// E.g. if user happens to define type named `Option`, we would have false positive
|
||||
match name.to_string().as_str() {
|
||||
"Option" => Some(TryKind::Option),
|
||||
"Result" => Some(TryKind::Result { ty }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FlowKind {
|
||||
fn make_result_handler(&self, expr: Option<ast::Expr>) -> ast::Expr {
|
||||
match self {
|
||||
@ -357,22 +447,6 @@ impl FlowKind {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_kind_of_ty(ty: hir::Type, ctx: &AssistContext) -> Option<TryKind> {
|
||||
if ty.is_unknown() {
|
||||
// We favour Result for `expr?`
|
||||
return Some(TryKind::Result { ty });
|
||||
}
|
||||
let adt = ty.as_adt()?;
|
||||
let name = adt.name(ctx.db());
|
||||
// FIXME: use lang items to determine if it is std type or user defined
|
||||
// E.g. if user happens to define type named `Option`, we would have false positive
|
||||
match name.to_string().as_str() {
|
||||
"Option" => Some(TryKind::Option),
|
||||
"Result" => Some(TryKind::Result { ty }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionBody {
|
||||
fn parent(&self) -> Option<SyntaxNode> {
|
||||
match self {
|
||||
@ -525,130 +599,192 @@ impl FunctionBody {
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to guess what user wants to extract
|
||||
///
|
||||
/// We have basically have two cases:
|
||||
/// * We want whole node, like `loop {}`, `2 + 2`, `{ let n = 1; }` exprs.
|
||||
/// Then we can use `ast::Expr`
|
||||
/// * We want a few statements for a block. E.g.
|
||||
/// ```rust,no_run
|
||||
/// fn foo() -> i32 {
|
||||
/// let m = 1;
|
||||
/// $0
|
||||
/// let n = 2;
|
||||
/// let k = 3;
|
||||
/// k + n
|
||||
/// $0
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<FunctionBody> {
|
||||
if let Some(stmt) = ast::Stmt::cast(node.clone()) {
|
||||
return match stmt {
|
||||
ast::Stmt::Item(_) => None,
|
||||
ast::Stmt::ExprStmt(_) | ast::Stmt::LetStmt(_) => Some(FunctionBody::from_range(
|
||||
node.parent().and_then(ast::BlockExpr::cast)?,
|
||||
node.text_range(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
let expr = ast::Expr::cast(node.clone())?;
|
||||
// A node got selected fully
|
||||
if node.text_range() == selection_range {
|
||||
return FunctionBody::from_expr(expr.clone());
|
||||
}
|
||||
|
||||
// Covering element returned the parent block of one or multiple statements that have been selected
|
||||
if let ast::Expr::BlockExpr(block) = expr {
|
||||
// Extract the full statements.
|
||||
return Some(FunctionBody::from_range(block, selection_range));
|
||||
}
|
||||
|
||||
node.ancestors().find_map(ast::Expr::cast).and_then(FunctionBody::from_expr)
|
||||
}
|
||||
|
||||
/// Analyzes a function body, returning the used local variables that are referenced in it as well as
|
||||
/// whether it contains an await expression.
|
||||
fn analyze_body(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
body: &FunctionBody,
|
||||
) -> (FxIndexSet<Local>, bool, Option<ast::SelfParam>) {
|
||||
// FIXME: currently usages inside macros are not found
|
||||
let mut has_await = false;
|
||||
let mut self_param = None;
|
||||
let mut res = FxIndexSet::default();
|
||||
body.walk_expr(&mut |expr| {
|
||||
has_await |= matches!(expr, ast::Expr::AwaitExpr(_));
|
||||
let name_ref = match expr {
|
||||
ast::Expr::PathExpr(path_expr) => {
|
||||
path_expr.path().and_then(|it| it.as_single_name_ref())
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if let Some(name_ref) = name_ref {
|
||||
if let Some(
|
||||
NameRefClass::Definition(Definition::Local(local_ref))
|
||||
| NameRefClass::FieldShorthand { local_ref, field_ref: _ },
|
||||
) = NameRefClass::classify(sema, &name_ref)
|
||||
{
|
||||
if local_ref.is_self(sema.db) {
|
||||
match local_ref.source(sema.db).value {
|
||||
Either::Right(it) => {
|
||||
stdx::always!(
|
||||
self_param.replace(it).is_none(),
|
||||
"body references two different self params"
|
||||
);
|
||||
}
|
||||
Either::Left(_) => {
|
||||
stdx::never!("Local::is_self returned true, but source is IdentPat");
|
||||
impl FunctionBody {
|
||||
/// Analyzes a function body, returning the used local variables that are referenced in it as well as
|
||||
/// whether it contains an await expression.
|
||||
fn analyze(
|
||||
&self,
|
||||
sema: &Semantics<RootDatabase>,
|
||||
) -> (FxIndexSet<Local>, bool, Option<ast::SelfParam>) {
|
||||
// FIXME: currently usages inside macros are not found
|
||||
let mut has_await = false;
|
||||
let mut self_param = None;
|
||||
let mut res = FxIndexSet::default();
|
||||
self.walk_expr(&mut |expr| {
|
||||
has_await |= matches!(expr, ast::Expr::AwaitExpr(_));
|
||||
let name_ref = match expr {
|
||||
ast::Expr::PathExpr(path_expr) => {
|
||||
path_expr.path().and_then(|it| it.as_single_name_ref())
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if let Some(name_ref) = name_ref {
|
||||
if let Some(
|
||||
NameRefClass::Definition(Definition::Local(local_ref))
|
||||
| NameRefClass::FieldShorthand { local_ref, field_ref: _ },
|
||||
) = NameRefClass::classify(sema, &name_ref)
|
||||
{
|
||||
if local_ref.is_self(sema.db) {
|
||||
match local_ref.source(sema.db).value {
|
||||
Either::Right(it) => {
|
||||
stdx::always!(
|
||||
self_param.replace(it).is_none(),
|
||||
"body references two different self params"
|
||||
);
|
||||
}
|
||||
Either::Left(_) => {
|
||||
stdx::never!(
|
||||
"Local::is_self returned true, but source is IdentPat"
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.insert(local_ref);
|
||||
}
|
||||
} else {
|
||||
res.insert(local_ref);
|
||||
}
|
||||
}
|
||||
});
|
||||
(res, has_await, self_param)
|
||||
}
|
||||
|
||||
fn return_ty(&self, ctx: &AssistContext) -> Option<RetType> {
|
||||
match self.tail_expr() {
|
||||
Some(expr) => ctx.sema.type_of_expr(&expr).map(TypeInfo::original).map(RetType::Expr),
|
||||
None => Some(RetType::Stmt),
|
||||
}
|
||||
});
|
||||
(res, has_await, self_param)
|
||||
}
|
||||
}
|
||||
|
||||
/// find variables that should be extracted as params
|
||||
///
|
||||
/// Computes additional info that affects param type and mutability
|
||||
fn extracted_function_params(
|
||||
ctx: &AssistContext,
|
||||
body: &FunctionBody,
|
||||
locals: impl Iterator<Item = Local>,
|
||||
) -> Vec<Param> {
|
||||
locals
|
||||
.map(|local| (local, local.source(ctx.db())))
|
||||
.filter(|(_, src)| is_defined_outside_of_body(ctx, body, src))
|
||||
.filter_map(|(local, src)| {
|
||||
if src.value.is_left() {
|
||||
Some(local)
|
||||
} else {
|
||||
stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|var| {
|
||||
let usages = LocalUsages::find(ctx, var);
|
||||
let ty = var.ty(ctx.db());
|
||||
let is_copy = ty.is_copy(ctx.db());
|
||||
Param {
|
||||
var,
|
||||
ty,
|
||||
has_usages_afterwards: has_usages_after_body(&usages, body),
|
||||
has_mut_inside_body: has_exclusive_usages(ctx, &usages, body),
|
||||
is_copy,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
/// Local variables defined inside `body` that are accessed outside of it
|
||||
fn ret_values<'a>(
|
||||
&self,
|
||||
ctx: &'a AssistContext,
|
||||
parent: &SyntaxNode,
|
||||
) -> impl Iterator<Item = OutlivedLocal> + 'a {
|
||||
let parent = parent.clone();
|
||||
let range = self.text_range();
|
||||
locals_defined_in_body(&ctx.sema, self)
|
||||
.into_iter()
|
||||
.filter_map(move |local| local_outlives_body(ctx, range, local, &parent))
|
||||
}
|
||||
|
||||
fn has_usages_after_body(usages: &LocalUsages, body: &FunctionBody) -> bool {
|
||||
usages.iter().any(|reference| body.precedes_range(reference.range))
|
||||
/// Analyses the function body for external control flow.
|
||||
fn external_control_flow(&self, ctx: &AssistContext) -> Option<ControlFlow> {
|
||||
let mut ret_expr = None;
|
||||
let mut try_expr = None;
|
||||
let mut break_expr = None;
|
||||
let mut continue_expr = None;
|
||||
|
||||
let mut loop_depth = 0;
|
||||
|
||||
self.preorder_expr(&mut |expr| {
|
||||
let expr = match expr {
|
||||
WalkEvent::Enter(e) => e,
|
||||
WalkEvent::Leave(
|
||||
ast::Expr::LoopExpr(_) | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_),
|
||||
) => {
|
||||
loop_depth -= 1;
|
||||
return false;
|
||||
}
|
||||
WalkEvent::Leave(_) => return false,
|
||||
};
|
||||
match expr {
|
||||
ast::Expr::LoopExpr(_) | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) => {
|
||||
loop_depth += 1;
|
||||
}
|
||||
ast::Expr::ReturnExpr(it) => {
|
||||
ret_expr = Some(it);
|
||||
}
|
||||
ast::Expr::TryExpr(it) => {
|
||||
try_expr = Some(it);
|
||||
}
|
||||
ast::Expr::BreakExpr(it) if loop_depth == 0 => {
|
||||
break_expr = Some(it);
|
||||
}
|
||||
ast::Expr::ContinueExpr(it) if loop_depth == 0 => {
|
||||
continue_expr = Some(it);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
let kind = match (try_expr, ret_expr, break_expr, continue_expr) {
|
||||
(Some(e), None, None, None) => {
|
||||
let func = e.syntax().ancestors().find_map(ast::Fn::cast)?;
|
||||
let def = ctx.sema.to_def(&func)?;
|
||||
let ret_ty = def.ret_type(ctx.db());
|
||||
let kind = TryKind::of_ty(ret_ty, ctx)?;
|
||||
|
||||
Some(FlowKind::Try { kind })
|
||||
}
|
||||
(Some(_), Some(r), None, None) => match r.expr() {
|
||||
Some(expr) => {
|
||||
if let Some(kind) = expr_err_kind(&expr, ctx) {
|
||||
Some(FlowKind::TryReturn { expr, kind })
|
||||
} else {
|
||||
cov_mark::hit!(external_control_flow_try_and_return_non_err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
(Some(_), _, _, _) => {
|
||||
cov_mark::hit!(external_control_flow_try_and_bc);
|
||||
return None;
|
||||
}
|
||||
(None, Some(r), None, None) => Some(FlowKind::Return(r.expr())),
|
||||
(None, Some(_), _, _) => {
|
||||
cov_mark::hit!(external_control_flow_return_and_bc);
|
||||
return None;
|
||||
}
|
||||
(None, None, Some(_), Some(_)) => {
|
||||
cov_mark::hit!(external_control_flow_break_and_continue);
|
||||
return None;
|
||||
}
|
||||
(None, None, Some(b), None) => Some(FlowKind::Break(b.expr())),
|
||||
(None, None, None, Some(_)) => Some(FlowKind::Continue),
|
||||
(None, None, None, None) => None,
|
||||
};
|
||||
|
||||
Some(ControlFlow { kind })
|
||||
}
|
||||
/// find variables that should be extracted as params
|
||||
///
|
||||
/// Computes additional info that affects param type and mutability
|
||||
fn extracted_function_params(
|
||||
&self,
|
||||
ctx: &AssistContext,
|
||||
locals: impl Iterator<Item = Local>,
|
||||
) -> Vec<Param> {
|
||||
locals
|
||||
.map(|local| (local, local.source(ctx.db())))
|
||||
.filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
|
||||
.filter_map(|(local, src)| {
|
||||
if src.value.is_left() {
|
||||
Some(local)
|
||||
} else {
|
||||
stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|var| {
|
||||
let usages = LocalUsages::find(ctx, var);
|
||||
let ty = var.ty(ctx.db());
|
||||
let is_copy = ty.is_copy(ctx.db());
|
||||
Param {
|
||||
var,
|
||||
ty,
|
||||
has_usages_afterwards: self.has_usages_after_body(&usages),
|
||||
has_mut_inside_body: has_exclusive_usages(ctx, &usages, self),
|
||||
is_copy,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn has_usages_after_body(&self, usages: &LocalUsages) -> bool {
|
||||
usages.iter().any(|reference| self.precedes_range(reference.range))
|
||||
}
|
||||
}
|
||||
|
||||
/// checks if relevant var is used with `&mut` access inside body
|
||||
@ -801,19 +937,6 @@ fn locals_defined_in_body(
|
||||
res
|
||||
}
|
||||
|
||||
/// Local variables defined inside `body` that are accessed outside of it
|
||||
fn ret_values<'a>(
|
||||
ctx: &'a AssistContext,
|
||||
body: &FunctionBody,
|
||||
parent: &SyntaxNode,
|
||||
) -> impl Iterator<Item = OutlivedLocal> + 'a {
|
||||
let parent = parent.clone();
|
||||
let range = body.text_range();
|
||||
locals_defined_in_body(&ctx.sema, body)
|
||||
.into_iter()
|
||||
.filter_map(move |local| local_outlives_body(ctx, range, local, &parent))
|
||||
}
|
||||
|
||||
/// Returns usage details if local variable is used after(outside of) body
|
||||
fn local_outlives_body(
|
||||
ctx: &AssistContext,
|
||||
@ -856,95 +979,6 @@ fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
|
||||
}
|
||||
}
|
||||
|
||||
fn body_return_ty(ctx: &AssistContext, body: &FunctionBody) -> Option<RetType> {
|
||||
match body.tail_expr() {
|
||||
Some(expr) => ctx.sema.type_of_expr(&expr).map(TypeInfo::original).map(RetType::Expr),
|
||||
None => Some(RetType::Stmt),
|
||||
}
|
||||
}
|
||||
|
||||
/// Analyses the function body for external control flow.
|
||||
fn external_control_flow(ctx: &AssistContext, body: &FunctionBody) -> Option<ControlFlow> {
|
||||
let mut ret_expr = None;
|
||||
let mut try_expr = None;
|
||||
let mut break_expr = None;
|
||||
let mut continue_expr = None;
|
||||
|
||||
let mut loop_depth = 0;
|
||||
|
||||
body.preorder_expr(&mut |expr| {
|
||||
let expr = match expr {
|
||||
WalkEvent::Enter(e) => e,
|
||||
WalkEvent::Leave(
|
||||
ast::Expr::LoopExpr(_) | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_),
|
||||
) => {
|
||||
loop_depth -= 1;
|
||||
return false;
|
||||
}
|
||||
WalkEvent::Leave(_) => return false,
|
||||
};
|
||||
match expr {
|
||||
ast::Expr::LoopExpr(_) | ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) => {
|
||||
loop_depth += 1;
|
||||
}
|
||||
ast::Expr::ReturnExpr(it) => {
|
||||
ret_expr = Some(it);
|
||||
}
|
||||
ast::Expr::TryExpr(it) => {
|
||||
try_expr = Some(it);
|
||||
}
|
||||
ast::Expr::BreakExpr(it) if loop_depth == 0 => {
|
||||
break_expr = Some(it);
|
||||
}
|
||||
ast::Expr::ContinueExpr(it) if loop_depth == 0 => {
|
||||
continue_expr = Some(it);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
false
|
||||
});
|
||||
|
||||
let kind = match (try_expr, ret_expr, break_expr, continue_expr) {
|
||||
(Some(e), None, None, None) => {
|
||||
let func = e.syntax().ancestors().find_map(ast::Fn::cast)?;
|
||||
let def = ctx.sema.to_def(&func)?;
|
||||
let ret_ty = def.ret_type(ctx.db());
|
||||
let kind = try_kind_of_ty(ret_ty, ctx)?;
|
||||
|
||||
Some(FlowKind::Try { kind })
|
||||
}
|
||||
(Some(_), Some(r), None, None) => match r.expr() {
|
||||
Some(expr) => {
|
||||
if let Some(kind) = expr_err_kind(&expr, ctx) {
|
||||
Some(FlowKind::TryReturn { expr, kind })
|
||||
} else {
|
||||
cov_mark::hit!(external_control_flow_try_and_return_non_err);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
(Some(_), _, _, _) => {
|
||||
cov_mark::hit!(external_control_flow_try_and_bc);
|
||||
return None;
|
||||
}
|
||||
(None, Some(r), None, None) => Some(FlowKind::Return(r.expr())),
|
||||
(None, Some(_), _, _) => {
|
||||
cov_mark::hit!(external_control_flow_return_and_bc);
|
||||
return None;
|
||||
}
|
||||
(None, None, Some(_), Some(_)) => {
|
||||
cov_mark::hit!(external_control_flow_break_and_continue);
|
||||
return None;
|
||||
}
|
||||
(None, None, Some(b), None) => Some(FlowKind::Break(b.expr())),
|
||||
(None, None, None, Some(_)) => Some(FlowKind::Continue),
|
||||
(None, None, None, None) => None,
|
||||
};
|
||||
|
||||
Some(ControlFlow { kind })
|
||||
}
|
||||
|
||||
/// Checks is expr is `Err(_)` or `None`
|
||||
fn expr_err_kind(expr: &ast::Expr, ctx: &AssistContext) -> Option<TryKind> {
|
||||
let func_name = match expr {
|
||||
@ -1020,7 +1054,7 @@ fn make_call(
|
||||
let expr = handler.make_call_expr(call_expr).indent(indent);
|
||||
|
||||
let mut buf = String::new();
|
||||
match fun.vars_defined_in_body_and_outlive.as_slice() {
|
||||
match fun.outliving_locals.as_slice() {
|
||||
[] => {}
|
||||
[var] => {
|
||||
format_to!(buf, "let {}{} = ", mut_modifier(var), var.local.name(ctx.db()).unwrap())
|
||||
@ -1045,9 +1079,7 @@ fn make_call(
|
||||
if body_contains_await {
|
||||
buf.push_str(".await");
|
||||
}
|
||||
if fun.ret_ty.is_unit()
|
||||
&& (!fun.vars_defined_in_body_and_outlive.is_empty() || !expr.is_block_like())
|
||||
{
|
||||
if fun.ret_ty.is_unit() && (!fun.outliving_locals.is_empty() || !expr.is_block_like()) {
|
||||
buf.push(';');
|
||||
}
|
||||
buf
|
||||
@ -1178,11 +1210,12 @@ fn format_function(
|
||||
old_indent: IndentLevel,
|
||||
new_indent: IndentLevel,
|
||||
body_contains_await: bool,
|
||||
in_tail: bool,
|
||||
) -> String {
|
||||
let mut fn_def = String::new();
|
||||
let params = make_param_list(ctx, module, fun);
|
||||
let ret_ty = make_ret_ty(ctx, module, fun);
|
||||
let body = make_body(ctx, old_indent, new_indent, fun);
|
||||
let params = fun.make_param_list(ctx, module);
|
||||
let ret_ty = fun.make_ret_ty(ctx, module, in_tail);
|
||||
let body = make_body(ctx, old_indent, new_indent, fun, in_tail);
|
||||
let async_kw = if body_contains_await { "async " } else { "" };
|
||||
match ctx.config.snippet_cap {
|
||||
Some(_) => format_to!(fn_def, "\n\n{}{}fn $0{}{}", new_indent, async_kw, fun.name, params),
|
||||
@ -1196,10 +1229,59 @@ fn format_function(
|
||||
fn_def
|
||||
}
|
||||
|
||||
fn make_param_list(ctx: &AssistContext, module: hir::Module, fun: &Function) -> ast::ParamList {
|
||||
let self_param = fun.self_param.clone();
|
||||
let params = fun.params.iter().map(|param| param.to_param(ctx, module));
|
||||
make::param_list(self_param, params)
|
||||
impl Function {
|
||||
fn make_param_list(&self, ctx: &AssistContext, module: hir::Module) -> ast::ParamList {
|
||||
let self_param = self.self_param.clone();
|
||||
let params = self.params.iter().map(|param| param.to_param(ctx, module));
|
||||
make::param_list(self_param, params)
|
||||
}
|
||||
|
||||
fn make_ret_ty(
|
||||
&self,
|
||||
ctx: &AssistContext,
|
||||
module: hir::Module,
|
||||
in_tail: bool,
|
||||
) -> Option<ast::RetType> {
|
||||
let fun_ty = self.return_type(ctx);
|
||||
let handler =
|
||||
if in_tail { FlowHandler::None } else { FlowHandler::from_ret_ty(self, &fun_ty) };
|
||||
let ret_ty = match &handler {
|
||||
FlowHandler::None => {
|
||||
if matches!(fun_ty, FunType::Unit) {
|
||||
return None;
|
||||
}
|
||||
fun_ty.make_ty(ctx, module)
|
||||
}
|
||||
FlowHandler::Try { kind: TryKind::Option } => {
|
||||
make::ext::ty_option(fun_ty.make_ty(ctx, module))
|
||||
}
|
||||
FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
|
||||
let handler_ty = parent_ret_ty
|
||||
.type_arguments()
|
||||
.nth(1)
|
||||
.map(|ty| make_ty(&ty, ctx, module))
|
||||
.unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
|
||||
}
|
||||
FlowHandler::If { .. } => make::ext::ty_bool(),
|
||||
FlowHandler::IfOption { action } => {
|
||||
let handler_ty = action
|
||||
.expr_ty(ctx)
|
||||
.map(|ty| make_ty(&ty, ctx, module))
|
||||
.unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_option(handler_ty)
|
||||
}
|
||||
FlowHandler::MatchOption { .. } => make::ext::ty_option(fun_ty.make_ty(ctx, module)),
|
||||
FlowHandler::MatchResult { err } => {
|
||||
let handler_ty = err
|
||||
.expr_ty(ctx)
|
||||
.map(|ty| make_ty(&ty, ctx, module))
|
||||
.unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
|
||||
}
|
||||
};
|
||||
Some(make::ret_type(ret_ty))
|
||||
}
|
||||
}
|
||||
|
||||
impl FunType {
|
||||
@ -1225,53 +1307,15 @@ impl FunType {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_ret_ty(ctx: &AssistContext, module: hir::Module, fun: &Function) -> Option<ast::RetType> {
|
||||
let fun_ty = fun.return_type(ctx);
|
||||
let handler = FlowHandler::from_ret_ty(fun, &fun_ty);
|
||||
let ret_ty = match &handler {
|
||||
FlowHandler::None => {
|
||||
if matches!(fun_ty, FunType::Unit) {
|
||||
return None;
|
||||
}
|
||||
fun_ty.make_ty(ctx, module)
|
||||
}
|
||||
FlowHandler::Try { kind: TryKind::Option } => {
|
||||
make::ext::ty_option(fun_ty.make_ty(ctx, module))
|
||||
}
|
||||
FlowHandler::Try { kind: TryKind::Result { ty: parent_ret_ty } } => {
|
||||
let handler_ty = parent_ret_ty
|
||||
.type_arguments()
|
||||
.nth(1)
|
||||
.map(|ty| make_ty(&ty, ctx, module))
|
||||
.unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
|
||||
}
|
||||
FlowHandler::If { .. } => make::ext::ty_bool(),
|
||||
FlowHandler::IfOption { action } => {
|
||||
let handler_ty = action
|
||||
.expr_ty(ctx)
|
||||
.map(|ty| make_ty(&ty, ctx, module))
|
||||
.unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_option(handler_ty)
|
||||
}
|
||||
FlowHandler::MatchOption { .. } => make::ext::ty_option(fun_ty.make_ty(ctx, module)),
|
||||
FlowHandler::MatchResult { err } => {
|
||||
let handler_ty =
|
||||
err.expr_ty(ctx).map(|ty| make_ty(&ty, ctx, module)).unwrap_or_else(make::ty_unit);
|
||||
make::ext::ty_result(fun_ty.make_ty(ctx, module), handler_ty)
|
||||
}
|
||||
};
|
||||
Some(make::ret_type(ret_ty))
|
||||
}
|
||||
|
||||
fn make_body(
|
||||
ctx: &AssistContext,
|
||||
old_indent: IndentLevel,
|
||||
new_indent: IndentLevel,
|
||||
fun: &Function,
|
||||
in_tail: bool,
|
||||
) -> ast::BlockExpr {
|
||||
let ret_ty = fun.return_type(ctx);
|
||||
let handler = FlowHandler::from_ret_ty(fun, &ret_ty);
|
||||
let handler = if in_tail { FlowHandler::None } else { FlowHandler::from_ret_ty(fun, &ret_ty) };
|
||||
let block = match &fun.body {
|
||||
FunctionBody::Expr(expr) => {
|
||||
let expr = rewrite_body_segment(ctx, &fun.params, &handler, expr.syntax());
|
||||
@ -1307,7 +1351,7 @@ fn make_body(
|
||||
};
|
||||
|
||||
if tail_expr.is_none() {
|
||||
match fun.vars_defined_in_body_and_outlive.as_slice() {
|
||||
match fun.outliving_locals.as_slice() {
|
||||
[] => {}
|
||||
[var] => {
|
||||
tail_expr = Some(path_expr_from_local(ctx, var.local));
|
||||
@ -3861,6 +3905,30 @@ fn $0fun_name() {
|
||||
foo();
|
||||
foo();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_does_not_wrap_res_in_res() {
|
||||
check_assist(
|
||||
extract_function,
|
||||
r#"
|
||||
//- minicore: result
|
||||
fn foo() -> Result<(), i64> {
|
||||
$0Result::<i32, i64>::Ok(0)?;
|
||||
Ok(())$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> Result<(), i64> {
|
||||
fun_name()?
|
||||
}
|
||||
|
||||
fn $0fun_name() -> Result<(), i64> {
|
||||
Result::<i32, i64>::Ok(0)?;
|
||||
Ok(())
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user