Skip to content

Commit ec75384

Browse files
bors[bot]Veykril
andauthored
Merge #9788
9788: fix: extract_function does not move locals defined outside of loops r=Veykril a=Veykril Fixes #8234 Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
2 parents 294cfd6 + 01413dd commit ec75384

File tree

4 files changed

+149
-21
lines changed

4 files changed

+149
-21
lines changed

crates/ide/src/syntax_highlighting.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,9 @@ pub struct HlRange {
138138
// injected:: Emitted for doc-string injected highlighting like rust source blocks in documentation.
139139
// intraDocLink:: Emitted for intra doc links in doc-strings.
140140
// library:: Emitted for items that are defined outside of the current crate.
141+
// mutable:: Emitted for mutable locals and statics as well as functions taking `&mut self`.
141142
// public:: Emitted for items that are from the current crate and are `pub`.
142-
// mutable:: Emitted for mutable locals and statics.
143+
// reference: Emitted for locals behind a reference and functions taking `self` by reference.
143144
// static:: Emitted for "static" functions, also known as functions that do not take a `self` param, as well as statics and consts.
144145
// trait:: Emitted for associated trait items.
145146
// unsafe:: Emitted for unsafe operations, like unsafe function calls, as well as the `unsafe` token.

crates/ide/src/syntax_highlighting/tags.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ pub enum HlTag {
4545
pub enum HlMod {
4646
/// Used for items in traits and impls.
4747
Associated = 0,
48+
/// Used with keywords like `async` and `await`.
49+
Async,
4850
/// Used to differentiate individual elements within attributes.
4951
Attribute,
5052
/// Callable item or value.
@@ -62,20 +64,18 @@ pub enum HlMod {
6264
Injected,
6365
/// Used for intra doc links in doc injection.
6466
IntraDocLink,
67+
/// Used for items from other crates.
68+
Library,
6569
/// Mutable binding.
6670
Mutable,
71+
/// Used for public items.
72+
Public,
6773
/// Immutable reference.
6874
Reference,
6975
/// Used for associated functions.
7076
Static,
7177
/// Used for items in traits and trait impls.
7278
Trait,
73-
/// Used with keywords like `async` and `await`.
74-
Async,
75-
/// Used for items from other crates.
76-
Library,
77-
/// Used for public items.
78-
Public,
7979
// Keep this last!
8080
/// Used for unsafe functions, unsafe traits, mutable statics, union accesses and unsafe operations.
8181
Unsafe,

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 118 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext) -> Option
9696
return;
9797
}
9898

99-
let params = body.extracted_function_params(ctx, locals_used.iter().copied());
99+
let params =
100+
body.extracted_function_params(ctx, &container_info, locals_used.iter().copied());
100101

101102
let fun = Function {
102103
name: "fun_name".to_string(),
@@ -183,8 +184,8 @@ struct Function {
183184
struct Param {
184185
var: Local,
185186
ty: hir::Type,
186-
has_usages_afterwards: bool,
187-
has_mut_inside_body: bool,
187+
move_local: bool,
188+
requires_mut: bool,
188189
is_copy: bool,
189190
}
190191

@@ -226,6 +227,7 @@ struct ControlFlow {
226227
struct ContainerInfo {
227228
is_const: bool,
228229
is_in_tail: bool,
230+
parent_loop: Option<SyntaxNode>,
229231
/// The function's return type, const's type etc.
230232
ret_type: Option<hir::Type>,
231233
}
@@ -335,11 +337,11 @@ impl ParamKind {
335337

336338
impl Param {
337339
fn kind(&self) -> ParamKind {
338-
match (self.has_usages_afterwards, self.has_mut_inside_body, self.is_copy) {
339-
(true, true, _) => ParamKind::MutRef,
340-
(true, false, false) => ParamKind::SharedRef,
341-
(false, true, _) => ParamKind::MutValue,
342-
(true, false, true) | (false, false, _) => ParamKind::Value,
340+
match (self.move_local, self.requires_mut, self.is_copy) {
341+
(false, true, _) => ParamKind::MutRef,
342+
(false, false, false) => ParamKind::SharedRef,
343+
(true, true, _) => ParamKind::MutValue,
344+
(_, false, _) => ParamKind::Value,
343345
}
344346
}
345347

@@ -622,6 +624,15 @@ impl FunctionBody {
622624
fn analyze_container(&self, sema: &Semantics<RootDatabase>) -> Option<ContainerInfo> {
623625
let mut ancestors = self.parent()?.ancestors();
624626
let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted);
627+
let mut parent_loop = None;
628+
let mut set_parent_loop = |loop_: &dyn ast::LoopBodyOwner| {
629+
if loop_
630+
.loop_body()
631+
.map_or(false, |it| it.syntax().text_range().contains_range(self.text_range()))
632+
{
633+
parent_loop.get_or_insert(loop_.syntax().clone());
634+
}
635+
};
625636
let (is_const, expr, ty) = loop {
626637
let anc = ancestors.next()?;
627638
break match_ast! {
@@ -658,6 +669,18 @@ impl FunctionBody {
658669
},
659670
ast::Variant(__) => return None,
660671
ast::Meta(__) => return None,
672+
ast::LoopExpr(it) => {
673+
set_parent_loop(&it);
674+
continue;
675+
},
676+
ast::ForExpr(it) => {
677+
set_parent_loop(&it);
678+
continue;
679+
},
680+
ast::WhileExpr(it) => {
681+
set_parent_loop(&it);
682+
continue;
683+
},
661684
_ => continue,
662685
}
663686
};
@@ -670,7 +693,7 @@ impl FunctionBody {
670693
container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| {
671694
container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range())
672695
});
673-
Some(ContainerInfo { is_in_tail, is_const, ret_type: ty })
696+
Some(ContainerInfo { is_in_tail, is_const, parent_loop, ret_type: ty })
674697
}
675698

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

781804
Some(ControlFlow { kind, is_async, is_unsafe: _is_unsafe })
782805
}
806+
783807
/// find variables that should be extracted as params
784808
///
785809
/// Computes additional info that affects param type and mutability
786810
fn extracted_function_params(
787811
&self,
788812
ctx: &AssistContext,
813+
container_info: &ContainerInfo,
789814
locals: impl Iterator<Item = Local>,
790815
) -> Vec<Param> {
791816
locals
792817
.map(|local| (local, local.source(ctx.db())))
793818
.filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
794819
.filter_map(|(local, src)| {
795-
if src.value.is_left() {
796-
Some(local)
820+
if let Either::Left(src) = src.value {
821+
Some((local, src))
797822
} else {
798823
stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
799824
None
800825
}
801826
})
802-
.map(|var| {
827+
.map(|(var, src)| {
803828
let usages = LocalUsages::find_local_usages(ctx, var);
804829
let ty = var.ty(ctx.db());
805830
let is_copy = ty.is_copy(ctx.db());
806831
Param {
807832
var,
808833
ty,
809-
has_usages_afterwards: self.has_usages_after_body(&usages),
810-
has_mut_inside_body: has_exclusive_usages(ctx, &usages, self),
834+
move_local: container_info.parent_loop.as_ref().map_or(true, |it| {
835+
it.text_range().contains_range(src.syntax().text_range())
836+
}) && !self.has_usages_after_body(&usages),
837+
requires_mut: has_exclusive_usages(ctx, &usages, self),
811838
is_copy,
812839
}
813840
})
@@ -4009,6 +4036,83 @@ const FOO: () = {
40094036
const fn $0fun_name() {
40104037
()
40114038
}
4039+
"#,
4040+
);
4041+
}
4042+
4043+
#[test]
4044+
fn extract_does_not_move_outer_loop_vars() {
4045+
check_assist(
4046+
extract_function,
4047+
r#"
4048+
fn foo() {
4049+
let mut x = 5;
4050+
for _ in 0..10 {
4051+
$0x += 1;$0
4052+
}
4053+
}
4054+
"#,
4055+
r#"
4056+
fn foo() {
4057+
let mut x = 5;
4058+
for _ in 0..10 {
4059+
fun_name(&mut x);
4060+
}
4061+
}
4062+
4063+
fn $0fun_name(x: &mut i32) {
4064+
*x += 1;
4065+
}
4066+
"#,
4067+
);
4068+
check_assist(
4069+
extract_function,
4070+
r#"
4071+
fn foo() {
4072+
for _ in 0..10 {
4073+
let mut x = 5;
4074+
$0x += 1;$0
4075+
}
4076+
}
4077+
"#,
4078+
r#"
4079+
fn foo() {
4080+
for _ in 0..10 {
4081+
let mut x = 5;
4082+
fun_name(x);
4083+
}
4084+
}
4085+
4086+
fn $0fun_name(mut x: i32) {
4087+
x += 1;
4088+
}
4089+
"#,
4090+
);
4091+
check_assist(
4092+
extract_function,
4093+
r#"
4094+
fn foo() {
4095+
loop {
4096+
let mut x = 5;
4097+
for _ in 0..10 {
4098+
$0x += 1;$0
4099+
}
4100+
}
4101+
}
4102+
"#,
4103+
r#"
4104+
fn foo() {
4105+
loop {
4106+
let mut x = 5;
4107+
for _ in 0..10 {
4108+
fun_name(&mut x);
4109+
}
4110+
}
4111+
}
4112+
4113+
fn $0fun_name(x: &mut i32) {
4114+
*x += 1;
4115+
}
40124116
"#,
40134117
);
40144118
}

crates/test_utils/src/minicore.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
//! deref_mut: deref
1818
//! index: sized
1919
//! fn:
20+
//! try:
2021
//! pin:
2122
//! future: pin
2223
//! option:
@@ -266,6 +267,28 @@ pub mod ops {
266267
}
267268
pub use self::function::{Fn, FnMut, FnOnce};
268269
// endregion:fn
270+
// region:try
271+
mod try_ {
272+
pub enum ControlFlow<B, C = ()> {
273+
Continue(C),
274+
Break(B),
275+
}
276+
pub trait FromResidual<R = Self::Residual> {
277+
#[lang = "from_residual"]
278+
fn from_residual(residual: R) -> Self;
279+
}
280+
#[lang = "try"]
281+
pub trait Try: FromResidual<Self::Residual> {
282+
type Output;
283+
type Residual;
284+
#[lang = "from_output"]
285+
fn from_output(output: Self::Output) -> Self;
286+
#[lang = "branch"]
287+
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
288+
}
289+
}
290+
pub use self::try_::{ControlFlow, FromResidual, Try};
291+
// endregion:try
269292
}
270293

271294
// region:eq

0 commit comments

Comments
 (0)