Skip to content

Commit ce1c853

Browse files
committed
Check param is not referenced in function
This checks the type param is referenced neither in the function body nor as a return type. * add tests
1 parent 95f5966 commit ce1c853

File tree

1 file changed

+82
-5
lines changed

1 file changed

+82
-5
lines changed

crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs

+82-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
use hir::{Semantics, TypeParam};
2+
use ide_db::{
3+
base_db::{FileId, FileRange},
4+
defs::Definition,
5+
search::SearchScope,
6+
RootDatabase,
7+
};
18
use syntax::{
29
ast::{self, make::impl_trait_type, HasGenericParams, HasName, HasTypeBounds},
310
ted, AstNode,
@@ -22,13 +29,12 @@ pub(crate) fn replace_named_generic_with_impl(
2229
) -> Option<()> {
2330
// finds `<P: AsRef<Path>>`
2431
let type_param = ctx.find_node_at_offset::<ast::TypeParam>()?;
32+
// returns `P`
33+
let type_param_name = type_param.name()?;
2534

2635
// The list of type bounds / traits: `AsRef<Path>`
2736
let type_bound_list = type_param.type_bound_list()?;
2837

29-
// returns `P`
30-
let type_param_name = type_param.name()?;
31-
3238
let fn_ = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
3339
let params = fn_
3440
.param_list()?
@@ -53,6 +59,11 @@ pub(crate) fn replace_named_generic_with_impl(
5359
return None;
5460
}
5561

62+
let type_param_hir_def = ctx.sema.to_def(&type_param)?;
63+
if is_referenced_outside(ctx.db(), type_param_hir_def, &fn_, ctx.file_id()) {
64+
return None;
65+
}
66+
5667
let target = type_param.syntax().text_range();
5768

5869
acc.add(
@@ -88,11 +99,36 @@ pub(crate) fn replace_named_generic_with_impl(
8899
)
89100
}
90101

102+
fn is_referenced_outside(
103+
db: &RootDatabase,
104+
type_param: TypeParam,
105+
fn_: &ast::Fn,
106+
file_id: FileId,
107+
) -> bool {
108+
let semantics = Semantics::new(db);
109+
let type_param_def = Definition::GenericParam(hir::GenericParam::TypeParam(type_param));
110+
111+
// limit search scope to function body & return type
112+
let search_ranges = vec![
113+
fn_.body().map(|body| body.syntax().text_range()),
114+
fn_.ret_type().map(|ret_type| ret_type.syntax().text_range()),
115+
];
116+
117+
search_ranges.into_iter().filter_map(|search_range| search_range).any(|search_range| {
118+
let file_range = FileRange { file_id, range: search_range };
119+
!type_param_def
120+
.usages(&semantics)
121+
.in_scope(SearchScope::file_range(file_range))
122+
.all()
123+
.is_empty()
124+
})
125+
}
126+
91127
#[cfg(test)]
92128
mod tests {
93129
use super::*;
94130

95-
use crate::tests::check_assist;
131+
use crate::tests::{check_assist, check_assist_not_applicable};
96132

97133
#[test]
98134
fn replace_generic_moves_into_function() {
@@ -122,12 +158,22 @@ mod tests {
122158
}
123159

124160
#[test]
125-
fn replace_generic_with_multiple_generic_names() {
161+
fn replace_generic_with_multiple_generic_params() {
126162
check_assist(
127163
replace_named_generic_with_impl,
128164
r#"fn new<P: AsRef<Path>, T$0: ToString>(t: T, p: P) -> Self {}"#,
129165
r#"fn new<P: AsRef<Path>>(t: impl ToString, p: P) -> Self {}"#,
130166
);
167+
check_assist(
168+
replace_named_generic_with_impl,
169+
r#"fn new<T$0: ToString, P: AsRef<Path>>(t: T, p: P) -> Self {}"#,
170+
r#"fn new<P: AsRef<Path>>(t: impl ToString, p: P) -> Self {}"#,
171+
);
172+
check_assist(
173+
replace_named_generic_with_impl,
174+
r#"fn new<A: Send, B$0: ToString, C: Debug>(a: A, b: B, c: C) -> Self {}"#,
175+
r#"fn new<A: Send, C: Debug>(a: A, b: impl ToString, c: C) -> Self {}"#,
176+
);
131177
}
132178

133179
#[test]
@@ -138,4 +184,35 @@ mod tests {
138184
r#"fn new(p: impl Send + Sync) -> Self {}"#,
139185
);
140186
}
187+
188+
#[test]
189+
fn replace_generic_not_applicable_if_param_used_as_return_type() {
190+
check_assist_not_applicable(
191+
replace_named_generic_with_impl,
192+
r#"fn new<P$0: Send + Sync>(p: P) -> P {}"#,
193+
);
194+
}
195+
196+
#[test]
197+
fn replace_generic_not_applicable_if_param_used_in_fn_body() {
198+
check_assist_not_applicable(
199+
replace_named_generic_with_impl,
200+
r#"fn new<P$0: ToString>(p: P) { let x: &dyn P = &O; }"#,
201+
);
202+
}
203+
204+
#[test]
205+
fn replace_generic_ignores_another_function_with_same_param_type() {
206+
check_assist(
207+
replace_named_generic_with_impl,
208+
r#"
209+
fn new<P$0: Send + Sync>(p: P) {}
210+
fn hello<P: Debug>(p: P) { println!("{:?}", p); }
211+
"#,
212+
r#"
213+
fn new(p: impl Send + Sync) {}
214+
fn hello<P: Debug>(p: P) { println!("{:?}", p); }
215+
"#,
216+
);
217+
}
141218
}

0 commit comments

Comments
 (0)