Skip to content

Commit 1a50f90

Browse files
committed
Reject recursive calls in inline_call
1 parent 1ccb21a commit 1a50f90

File tree

2 files changed

+67
-21
lines changed

2 files changed

+67
-21
lines changed

crates/ide_assists/src/handlers/inline_call.rs

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use ast::make;
22
use hir::{db::HirDatabase, HasSource, PathResolution, Semantics, TypeInfo};
33
use ide_db::{
4-
base_db::FileId, defs::Definition, path_transform::PathTransform, search::FileReference,
4+
base_db::{FileId, FileRange},
5+
defs::Definition,
6+
path_transform::PathTransform,
7+
search::{FileReference, SearchScope},
58
RootDatabase,
69
};
710
use itertools::izip;
@@ -54,31 +57,43 @@ use crate::{
5457
// }
5558
// ```
5659
pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
60+
let def_file = ctx.frange.file_id;
5761
let name = ctx.find_node_at_offset::<ast::Name>()?;
58-
let func_syn = name.syntax().parent().and_then(ast::Fn::cast)?;
59-
let func_body = func_syn.body()?;
60-
let param_list = func_syn.param_list()?;
61-
let function = ctx.sema.to_def(&func_syn)?;
62+
let ast_func = name.syntax().parent().and_then(ast::Fn::cast)?;
63+
let func_body = ast_func.body()?;
64+
let param_list = ast_func.param_list()?;
65+
66+
let function = ctx.sema.to_def(&ast_func)?;
67+
6268
let params = get_fn_params(ctx.sema.db, function, &param_list)?;
6369

6470
let usages = Definition::ModuleDef(hir::ModuleDef::Function(function)).usages(&ctx.sema);
6571
if !usages.at_least_one() {
6672
return None;
6773
}
6874

75+
let is_recursive_fn = usages
76+
.clone()
77+
.in_scope(SearchScope::file_range(FileRange {
78+
file_id: def_file,
79+
range: func_body.syntax().text_range(),
80+
}))
81+
.at_least_one();
82+
if is_recursive_fn {
83+
cov_mark::hit!(inline_into_callers_recursive);
84+
return None;
85+
}
86+
6987
acc.add(
7088
AssistId("inline_into_callers", AssistKind::RefactorInline),
7189
"Inline into all callers",
7290
name.syntax().text_range(),
7391
|builder| {
74-
let def_file = ctx.frange.file_id;
75-
let usages =
76-
Definition::ModuleDef(hir::ModuleDef::Function(function)).usages(&ctx.sema);
7792
let mut usages = usages.all();
7893
let current_file_usage = usages.references.remove(&def_file);
7994

80-
let mut can_remove = true;
81-
let mut inline_refs = |file_id, refs: Vec<FileReference>| {
95+
let mut remove_def = true;
96+
let mut inline_refs_for_file = |file_id, refs: Vec<FileReference>| {
8297
builder.edit_file(file_id);
8398
let count = refs.len();
8499
let name_refs = refs.into_iter().filter_map(|file_ref| match file_ref.name {
@@ -124,18 +139,18 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext) -> Opt
124139
);
125140
})
126141
.count();
127-
can_remove &= replaced == count;
142+
remove_def &= replaced == count;
128143
};
129144
for (file_id, refs) in usages.into_iter() {
130-
inline_refs(file_id, refs);
145+
inline_refs_for_file(file_id, refs);
131146
}
132147
if let Some(refs) = current_file_usage {
133-
inline_refs(def_file, refs);
148+
inline_refs_for_file(def_file, refs);
134149
} else {
135150
builder.edit_file(def_file);
136151
}
137-
if can_remove {
138-
builder.delete(func_syn.syntax().text_range());
152+
if remove_def {
153+
builder.delete(ast_func.syntax().text_range());
139154
}
140155
},
141156
)
@@ -201,10 +216,15 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
201216
)
202217
};
203218

204-
let hir::InFile { value: function_source, file_id } = function.source(ctx.db())?;
205-
let fn_body = function_source.body()?;
206-
let param_list = function_source.param_list()?;
219+
let fn_source = function.source(ctx.db())?;
220+
let fn_body = fn_source.value.body()?;
221+
let param_list = fn_source.value.param_list()?;
207222

223+
let FileRange { file_id, range } = fn_source.syntax().original_file_range(ctx.sema.db);
224+
if file_id == ctx.frange.file_id && range.contains(ctx.frange.range.start()) {
225+
cov_mark::hit!(inline_call_recursive);
226+
return None;
227+
}
208228
let params = get_fn_params(ctx.sema.db, function, &param_list)?;
209229

210230
if call_info.arguments.len() != params.len() {
@@ -220,7 +240,6 @@ pub(crate) fn inline_call(acc: &mut Assists, ctx: &AssistContext) -> Option<()>
220240
label,
221241
syntax.text_range(),
222242
|builder| {
223-
let file_id = file_id.original_file(ctx.sema.db);
224243
let replacement = inline(&ctx.sema, file_id, function, &fn_body, &params, &call_info);
225244

226245
builder.replace_ast(
@@ -967,6 +986,32 @@ fn foo() {
967986
foo * 0 + foo
968987
};
969988
}
989+
"#,
990+
);
991+
}
992+
993+
#[test]
994+
fn inline_callers_recursive() {
995+
cov_mark::check!(inline_into_callers_recursive);
996+
check_assist_not_applicable(
997+
inline_into_callers,
998+
r#"
999+
fn foo$0() {
1000+
foo();
1001+
}
1002+
"#,
1003+
);
1004+
}
1005+
1006+
#[test]
1007+
fn inline_call_recursive() {
1008+
cov_mark::check!(inline_call_recursive);
1009+
check_assist_not_applicable(
1010+
inline_call,
1011+
r#"
1012+
fn foo() {
1013+
foo$0();
1014+
}
9701015
"#,
9711016
);
9721017
}

crates/ide_db/src/search.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ impl Definition {
315315
}
316316
}
317317

318+
#[derive(Clone)]
318319
pub struct FindUsages<'a> {
319320
def: Definition,
320321
sema: &'a Semantics<'a, RootDatabase>,
@@ -341,7 +342,7 @@ impl<'a> FindUsages<'a> {
341342
self
342343
}
343344

344-
pub fn at_least_one(self) -> bool {
345+
pub fn at_least_one(&self) -> bool {
345346
let mut found = false;
346347
self.search(&mut |_, _| {
347348
found = true;
@@ -359,7 +360,7 @@ impl<'a> FindUsages<'a> {
359360
res
360361
}
361362

362-
fn search(self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
363+
fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
363364
let _p = profile::span("FindUsages:search");
364365
let sema = self.sema;
365366

0 commit comments

Comments
 (0)