Skip to content

Commit e7a8977

Browse files
bors[bot]BrandoniDawer
authored
Merge #8524 #8527
8524: Fix extract function with partial block selection r=matklad a=brandondong **Reproduction:** ```rust fn foo() { let n = 1; let mut v = $0n * n;$0 v += 1; } ``` 1. Select the snippet ($0) and use the "Extract into function" assist. 2. Extracted function is incorrect and does not compile: ```rust fn foo() { let n = 1; let mut v = fun_name(n); v += 1; } fn fun_name(n: i32) {} ``` 3. Omitting the ending semicolon from the selection fixes the extracted function: ```rust fn fun_name(n: i32) -> i32 { n * n } ``` **Cause:** - When `extraction_target` uses a block extraction (semicolon case) instead of an expression extraction (no semicolon case), the user selection is directly used as the TextRange. - However, the existing function extraction logic for blocks requires that the TextRange spans from start to end of complete statements to work correctly. - For example: ```rust fn foo() { let m = 2; let n = 1; let mut v = m $0* n; let mut w = 3;$0 v += 1; w += 1; } ``` produces ```rust fn foo() { let m = 2; let n = 1; let mut v = m let mut w = fun_name(n); v += 1; w += 1; } fn fun_name(n: i32) -> i32 { let mut w = 3; w } ``` - The user selected TextRange is directly replaced by the function call which is now in the middle of another statement. The extracted function body only contains statements that were fully covered by the TextRange and so the `* n` code is deleted. The logic for calculating variable usage and outlived variables for the function parameters and return type respectively search within the TextRange and so do not include `m` or `v`. **Fix:** - Only extract full statements when using block extraction. If a user selected part of a statement, extract that full statement. 8527: Switch introduce_named_lifetime assist to use mutable syntax tree r=matklad a=iDawer This extends `GenericParamsOwnerEdit` trait with `get_or_create_generic_param_list` method Co-authored-by: Brandon <brandondong604@hotmail.com> Co-authored-by: Dawer <7803845+iDawer@users.noreply.github.com>
3 parents e4f7f1e + ffefbf2 + cedbf2e commit e7a8977

File tree

4 files changed

+298
-38
lines changed

4 files changed

+298
-38
lines changed

crates/ide_assists/src/handlers/extract_function.rs

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -599,7 +599,12 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
599599
// we have selected a few statements in a block
600600
// so covering_element returns the whole block
601601
if node.kind() == BLOCK_EXPR {
602-
let body = FunctionBody::from_range(node.clone(), selection_range);
602+
// Extract the full statements.
603+
let statements_range = node
604+
.children()
605+
.filter(|c| selection_range.intersect(c.text_range()).is_some())
606+
.fold(selection_range, |acc, c| acc.cover(c.text_range()));
607+
let body = FunctionBody::from_range(node.clone(), statements_range);
603608
if body.is_some() {
604609
return body;
605610
}
@@ -610,7 +615,8 @@ fn extraction_target(node: &SyntaxNode, selection_range: TextRange) -> Option<Fu
610615
// so we try to expand covering_element to parent and repeat the previous
611616
if let Some(parent) = node.parent() {
612617
if parent.kind() == BLOCK_EXPR {
613-
let body = FunctionBody::from_range(parent, selection_range);
618+
// Extract the full statement.
619+
let body = FunctionBody::from_range(parent, node.text_range());
614620
if body.is_some() {
615621
return body;
616622
}
@@ -1784,6 +1790,60 @@ fn $0fun_name() -> i32 {
17841790
);
17851791
}
17861792

1793+
#[test]
1794+
fn extract_partial_block_single_line() {
1795+
check_assist(
1796+
extract_function,
1797+
r#"
1798+
fn foo() {
1799+
let n = 1;
1800+
let mut v = $0n * n;$0
1801+
v += 1;
1802+
}"#,
1803+
r#"
1804+
fn foo() {
1805+
let n = 1;
1806+
let mut v = fun_name(n);
1807+
v += 1;
1808+
}
1809+
1810+
fn $0fun_name(n: i32) -> i32 {
1811+
let mut v = n * n;
1812+
v
1813+
}"#,
1814+
);
1815+
}
1816+
1817+
#[test]
1818+
fn extract_partial_block() {
1819+
check_assist(
1820+
extract_function,
1821+
r#"
1822+
fn foo() {
1823+
let m = 2;
1824+
let n = 1;
1825+
let mut v = m $0* n;
1826+
let mut w = 3;$0
1827+
v += 1;
1828+
w += 1;
1829+
}"#,
1830+
r#"
1831+
fn foo() {
1832+
let m = 2;
1833+
let n = 1;
1834+
let (mut v, mut w) = fun_name(m, n);
1835+
v += 1;
1836+
w += 1;
1837+
}
1838+
1839+
fn $0fun_name(m: i32, n: i32) -> (i32, i32) {
1840+
let mut v = m * n;
1841+
let mut w = 3;
1842+
(v, w)
1843+
}"#,
1844+
);
1845+
}
1846+
17871847
#[test]
17881848
fn argument_form_expr() {
17891849
check_assist(

crates/ide_assists/src/handlers/introduce_named_lifetime.rs

Lines changed: 76 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use rustc_hash::FxHashSet;
22
use syntax::{
3-
ast::{self, GenericParamsOwner, NameOwner},
4-
AstNode, TextRange, TextSize,
3+
ast::{self, edit_in_place::GenericParamsOwnerEdit, make, GenericParamsOwner},
4+
ted::{self, Position},
5+
AstNode, TextRange,
56
};
67

78
use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
@@ -37,10 +38,12 @@ static ASSIST_LABEL: &str = "Introduce named lifetime";
3738
pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
3839
let lifetime =
3940
ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
41+
let lifetime_loc = lifetime.lifetime_ident_token()?.text_range();
42+
4043
if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
41-
generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range())
44+
generate_fn_def_assist(acc, fn_def, lifetime_loc, lifetime)
4245
} else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
43-
generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range())
46+
generate_impl_def_assist(acc, impl_def, lifetime_loc, lifetime)
4447
} else {
4548
None
4649
}
@@ -49,26 +52,26 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -
4952
/// Generate the assist for the fn def case
5053
fn generate_fn_def_assist(
5154
acc: &mut Assists,
52-
fn_def: &ast::Fn,
55+
fn_def: ast::Fn,
5356
lifetime_loc: TextRange,
57+
lifetime: ast::Lifetime,
5458
) -> Option<()> {
5559
let param_list: ast::ParamList = fn_def.param_list()?;
56-
let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
57-
let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
60+
let new_lifetime_param = generate_unique_lifetime_param_name(fn_def.generic_param_list())?;
5861
let self_param =
5962
// use the self if it's a reference and has no explicit lifetime
6063
param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
6164
// compute the location which implicitly has the same lifetime as the anonymous lifetime
6265
let loc_needing_lifetime = if let Some(self_param) = self_param {
6366
// if we have a self reference, use that
64-
Some(self_param.name()?.syntax().text_range().start())
67+
Some(NeedsLifetime::SelfParam(self_param))
6568
} else {
6669
// otherwise, if there's a single reference parameter without a named liftime, use that
6770
let fn_params_without_lifetime: Vec<_> = param_list
6871
.params()
6972
.filter_map(|param| match param.ty() {
7073
Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71-
Some(ascribed_type.amp_token()?.text_range().end())
74+
Some(NeedsLifetime::RefType(ascribed_type))
7275
}
7376
_ => None,
7477
})
@@ -81,30 +84,46 @@ fn generate_fn_def_assist(
8184
}
8285
};
8386
acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
84-
add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
85-
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
86-
loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
87+
let fn_def = builder.make_ast_mut(fn_def);
88+
let lifetime = builder.make_ast_mut(lifetime);
89+
let loc_needing_lifetime =
90+
loc_needing_lifetime.and_then(|it| it.make_mut(builder).to_position());
91+
92+
add_lifetime_param(fn_def.get_or_create_generic_param_list(), new_lifetime_param);
93+
ted::replace(
94+
lifetime.syntax(),
95+
make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
96+
);
97+
loc_needing_lifetime.map(|position| {
98+
ted::insert(position, make_ast_lifetime(new_lifetime_param).clone_for_update().syntax())
99+
});
87100
})
88101
}
89102

90103
/// Generate the assist for the impl def case
91104
fn generate_impl_def_assist(
92105
acc: &mut Assists,
93-
impl_def: &ast::Impl,
106+
impl_def: ast::Impl,
94107
lifetime_loc: TextRange,
108+
lifetime: ast::Lifetime,
95109
) -> Option<()> {
96-
let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
97-
let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
110+
let new_lifetime_param = generate_unique_lifetime_param_name(impl_def.generic_param_list())?;
98111
acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
99-
add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
100-
builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
112+
let impl_def = builder.make_ast_mut(impl_def);
113+
let lifetime = builder.make_ast_mut(lifetime);
114+
115+
add_lifetime_param(impl_def.get_or_create_generic_param_list(), new_lifetime_param);
116+
ted::replace(
117+
lifetime.syntax(),
118+
make_ast_lifetime(new_lifetime_param).clone_for_update().syntax(),
119+
);
101120
})
102121
}
103122

104123
/// Given a type parameter list, generate a unique lifetime parameter name
105124
/// which is not in the list
106125
fn generate_unique_lifetime_param_name(
107-
existing_type_param_list: &Option<ast::GenericParamList>,
126+
existing_type_param_list: Option<ast::GenericParamList>,
108127
) -> Option<char> {
109128
match existing_type_param_list {
110129
Some(type_params) => {
@@ -118,25 +137,37 @@ fn generate_unique_lifetime_param_name(
118137
}
119138
}
120139

121-
/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
122-
/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
123-
fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
124-
type_params_owner: &TypeParamsOwner,
125-
builder: &mut AssistBuilder,
126-
new_type_params_loc: TextSize,
127-
new_lifetime_param: char,
128-
) {
129-
match type_params_owner.generic_param_list() {
130-
// add the new lifetime parameter to an existing type param list
131-
Some(type_params) => {
132-
builder.insert(
133-
(u32::from(type_params.syntax().text_range().end()) - 1).into(),
134-
format!(", '{}", new_lifetime_param),
135-
);
140+
fn add_lifetime_param(type_params: ast::GenericParamList, new_lifetime_param: char) {
141+
let generic_param =
142+
make::generic_param(format!("'{}", new_lifetime_param), None).clone_for_update();
143+
type_params.add_generic_param(generic_param);
144+
}
145+
146+
fn make_ast_lifetime(new_lifetime_param: char) -> ast::Lifetime {
147+
make::generic_param(format!("'{}", new_lifetime_param), None)
148+
.syntax()
149+
.descendants()
150+
.find_map(ast::Lifetime::cast)
151+
.unwrap()
152+
}
153+
154+
enum NeedsLifetime {
155+
SelfParam(ast::SelfParam),
156+
RefType(ast::RefType),
157+
}
158+
159+
impl NeedsLifetime {
160+
fn make_mut(self, builder: &mut AssistBuilder) -> Self {
161+
match self {
162+
Self::SelfParam(it) => Self::SelfParam(builder.make_ast_mut(it)),
163+
Self::RefType(it) => Self::RefType(builder.make_ast_mut(it)),
136164
}
137-
// create a new type param list containing only the new lifetime parameter
138-
None => {
139-
builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
165+
}
166+
167+
fn to_position(self) -> Option<Position> {
168+
match self {
169+
Self::SelfParam(it) => Some(Position::after(it.amp_token()?)),
170+
Self::RefType(it) => Some(Position::after(it.amp_token()?)),
140171
}
141172
}
142173
}
@@ -312,4 +343,13 @@ mod tests {
312343
r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313344
);
314345
}
346+
347+
#[test]
348+
fn test_function_add_lifetime_to_self_ref_mut() {
349+
check_assist(
350+
introduce_named_lifetime,
351+
r#"fn foo(&mut self) -> &'_$0 ()"#,
352+
r#"fn foo<'a>(&'a mut self) -> &'a ()"#,
353+
);
354+
}
315355
}

0 commit comments

Comments
 (0)