Skip to content

fix: extract_function does not move locals defined outside of loops #9788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/ide/src/syntax_highlighting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,9 @@ pub struct HlRange {
// injected:: Emitted for doc-string injected highlighting like rust source blocks in documentation.
// intraDocLink:: Emitted for intra doc links in doc-strings.
// library:: Emitted for items that are defined outside of the current crate.
// mutable:: Emitted for mutable locals and statics as well as functions taking `&mut self`.
// public:: Emitted for items that are from the current crate and are `pub`.
// mutable:: Emitted for mutable locals and statics.
// reference: Emitted for locals behind a reference and functions taking `self` by reference.
// static:: Emitted for "static" functions, also known as functions that do not take a `self` param, as well as statics and consts.
// trait:: Emitted for associated trait items.
// unsafe:: Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.
Expand Down
12 changes: 6 additions & 6 deletions crates/ide/src/syntax_highlighting/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub enum HlTag {
pub enum HlMod {
/// Used for items in traits and impls.
Associated = 0,
/// Used with keywords like `async` and `await`.
Async,
/// Used to differentiate individual elements within attributes.
Attribute,
/// Callable item or value.
Expand All @@ -62,20 +64,18 @@ pub enum HlMod {
Injected,
/// Used for intra doc links in doc injection.
IntraDocLink,
/// Used for items from other crates.
Library,
/// Mutable binding.
Mutable,
/// Used for public items.
Public,
/// Immutable reference.
Reference,
/// Used for associated functions.
Static,
/// Used for items in traits and trait impls.
Trait,
/// Used with keywords like `async` and `await`.
Async,
/// Used for items from other crates.
Library,
/// Used for public items.
Public,
// Keep this last!
/// Used for unsafe functions, unsafe traits, mutable statics, union accesses and unsafe operations.
Unsafe,
Expand Down
132 changes: 118 additions & 14 deletions crates/ide_assists/src/handlers/extract_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
return;
}

let params = body.extracted_function_params(ctx, locals_used.iter().copied());
let params =
body.extracted_function_params(ctx, &container_info, locals_used.iter().copied());

let fun = Function {
name: "fun_name".to_string(),
Expand Down Expand Up @@ -183,8 +184,8 @@ struct Function {
struct Param {
var: Local,
ty: hir::Type,
has_usages_afterwards: bool,
has_mut_inside_body: bool,
move_local: bool,
requires_mut: bool,
is_copy: bool,
}

Expand Down Expand Up @@ -226,6 +227,7 @@ struct ControlFlow {
struct ContainerInfo {
is_const: bool,
is_in_tail: bool,
parent_loop: Option<SyntaxNode>,
/// The function's return type, const's type etc.
ret_type: Option<hir::Type>,
}
Expand Down Expand Up @@ -335,11 +337,11 @@ impl ParamKind {

impl Param {
fn kind(&self) -> ParamKind {
match (self.has_usages_afterwards, self.has_mut_inside_body, self.is_copy) {
(true, true, _) => ParamKind::MutRef,
(true, false, false) => ParamKind::SharedRef,
(false, true, _) => ParamKind::MutValue,
(true, false, true) | (false, false, _) => ParamKind::Value,
match (self.move_local, self.requires_mut, self.is_copy) {
(false, true, _) => ParamKind::MutRef,
(false, false, false) => ParamKind::SharedRef,
(true, true, _) => ParamKind::MutValue,
(_, false, _) => ParamKind::Value,
}
}

Expand Down Expand Up @@ -622,6 +624,15 @@ impl FunctionBody {
fn analyze_container(&self, sema: &Semantics<RootDatabase>) -> Option<ContainerInfo> {
let mut ancestors = self.parent()?.ancestors();
let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted);
let mut parent_loop = None;
let mut set_parent_loop = |loop_: &dyn ast::LoopBodyOwner| {
if loop_
.loop_body()
.map_or(false, |it| it.syntax().text_range().contains_range(self.text_range()))
{
parent_loop.get_or_insert(loop_.syntax().clone());
}
};
let (is_const, expr, ty) = loop {
let anc = ancestors.next()?;
break match_ast! {
Expand Down Expand Up @@ -658,6 +669,18 @@ impl FunctionBody {
},
ast::Variant(__) => return None,
ast::Meta(__) => return None,
ast::LoopExpr(it) => {
set_parent_loop(&it);
continue;
},
ast::ForExpr(it) => {
set_parent_loop(&it);
continue;
},
ast::WhileExpr(it) => {
set_parent_loop(&it);
continue;
},
_ => continue,
}
};
Expand All @@ -670,7 +693,7 @@ impl FunctionBody {
container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| {
container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
});
Some(ContainerInfo { is_in_tail, is_const, ret_type: ty })
Some(ContainerInfo { is_in_tail, is_const, parent_loop, ret_type: ty })
}

fn return_ty(&self, ctx: &AssistContext) -> Option<RetType> {
Expand Down Expand Up @@ -780,34 +803,38 @@ impl FunctionBody {

Some(ControlFlow { kind, is_async, is_unsafe: _is_unsafe })
}

/// find variables that should be extracted as params
///
/// Computes additional info that affects param type and mutability
fn extracted_function_params(
&self,
ctx: &AssistContext,
container_info: &ContainerInfo,
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)
if let Either::Left(src) = src.value {
Some((local, src))
} else {
stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
None
}
})
.map(|var| {
.map(|(var, src)| {
let usages = LocalUsages::find_local_usages(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),
move_local: container_info.parent_loop.as_ref().map_or(true, |it| {
it.text_range().contains_range(src.syntax().text_range())
}) && !self.has_usages_after_body(&usages),
requires_mut: has_exclusive_usages(ctx, &usages, self),
is_copy,
}
})
Expand Down Expand Up @@ -4009,6 +4036,83 @@ const FOO: () = {
const fn $0fun_name() {
()
}
"#,
);
}

#[test]
fn extract_does_not_move_outer_loop_vars() {
check_assist(
extract_function,
r#"
fn foo() {
let mut x = 5;
for _ in 0..10 {
$0x += 1;$0
}
}
"#,
r#"
fn foo() {
let mut x = 5;
for _ in 0..10 {
fun_name(&mut x);
}
}

fn $0fun_name(x: &mut i32) {
*x += 1;
}
"#,
);
check_assist(
extract_function,
r#"
fn foo() {
for _ in 0..10 {
let mut x = 5;
$0x += 1;$0
}
}
"#,
r#"
fn foo() {
for _ in 0..10 {
let mut x = 5;
fun_name(x);
}
}

fn $0fun_name(mut x: i32) {
x += 1;
}
"#,
);
check_assist(
extract_function,
r#"
fn foo() {
loop {
let mut x = 5;
for _ in 0..10 {
$0x += 1;$0
}
}
}
"#,
r#"
fn foo() {
loop {
let mut x = 5;
for _ in 0..10 {
fun_name(&mut x);
}
}
}

fn $0fun_name(x: &mut i32) {
*x += 1;
}
"#,
);
}
Expand Down
23 changes: 23 additions & 0 deletions crates/test_utils/src/minicore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
//! deref_mut: deref
//! index: sized
//! fn:
//! try:
//! pin:
//! future: pin
//! option:
Expand Down Expand Up @@ -266,6 +267,28 @@ pub mod ops {
}
pub use self::function::{Fn, FnMut, FnOnce};
// endregion:fn
// region:try
mod try_ {
pub enum ControlFlow<B, C = ()> {
Continue(C),
Break(B),
}
pub trait FromResidual<R = Self::Residual> {
#[lang = "from_residual"]
fn from_residual(residual: R) -> Self;
}
#[lang = "try"]
pub trait Try: FromResidual<Self::Residual> {
type Output;
type Residual;
#[lang = "from_output"]
fn from_output(output: Self::Output) -> Self;
#[lang = "branch"]
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
}
}
pub use self::try_::{ControlFlow, FromResidual, Try};
// endregion:try
}

// region:eq
Expand Down