aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2020-11-29 17:44:26 +0000
committerLukas Wirth <[email protected]>2020-11-29 18:02:59 +0000
commit4c33ae33e96a0087f9f8451fc100b8681237d05f (patch)
treeffc3bbff43cb941700faca0e2b6d645eab36fdc4 /crates/ide
parentf4a77f34da3d0d849c1e0b78b95df77485a57a3d (diff)
Reject more cases of invalid parameter to self renames
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/references/rename.rs135
1 files changed, 122 insertions, 13 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index e15b9f88e..731457696 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -221,24 +221,47 @@ fn rename_to_self(
221 let source_file = sema.parse(position.file_id); 221 let source_file = sema.parse(position.file_id);
222 let syn = source_file.syntax(); 222 let syn = source_file.syntax();
223 223
224 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) 224 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
225 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
225 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 226 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?;
226 let params = 227 let param_range = fn_ast
227 fn_def.param_list().ok_or_else(|| RenameError("Method has no parameters".to_string()))?; 228 .param_list()
228 if params.self_param().is_some() { 229 .and_then(|p| p.params().next())
230 .ok_or_else(|| RenameError("Method has no parameters".to_string()))?
231 .syntax()
232 .text_range();
233 if !param_range.contains(position.offset) {
234 return Err(RenameError("Only the first parameter can be self".to_string()));
235 }
236
237 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
238 .and_then(|def| sema.to_def(&def))
239 .ok_or_else(|| RenameError("No impl block found for function".to_string()))?;
240 if fn_def.self_param(sema.db).is_some() {
229 return Err(RenameError("Method already has a self parameter".to_string())); 241 return Err(RenameError("Method already has a self parameter".to_string()));
230 } 242 }
243
244 let params = fn_def.params(sema.db);
231 let first_param = 245 let first_param =
232 params.params().next().ok_or_else(|| RenameError("Method has no parameters".into()))?; 246 params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
233 let mutable = match first_param.ty() { 247 let first_param_ty = first_param.ty();
234 Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), 248 let impl_ty = impl_block.target_ty(sema.db);
235 _ => return Err(RenameError("Not renaming other types".to_string())), 249 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
250 // if the impl is a ref to the type we can just match the `&T` with self directly
251 (first_param_ty.clone(), "self")
252 } else {
253 first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {
254 (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })
255 })
236 }; 256 };
237 257
258 if ty != impl_ty {
259 return Err(RenameError("Parameter type differs from impl block type".to_string()));
260 }
261
238 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 262 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)
239 .ok_or_else(|| RenameError("No reference found at position".to_string()))?; 263 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
240 264
241 let param_range = first_param.syntax().text_range();
242 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs 265 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
243 .into_iter() 266 .into_iter()
244 .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); 267 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
@@ -254,10 +277,7 @@ fn rename_to_self(
254 277
255 edits.push(SourceFileEdit { 278 edits.push(SourceFileEdit {
256 file_id: position.file_id, 279 file_id: position.file_id,
257 edit: TextEdit::replace( 280 edit: TextEdit::replace(param_range, String::from(self_param)),
258 param_range,
259 String::from(if mutable { "&mut self" } else { "&self" }),
260 ),
261 }); 281 });
262 282
263 Ok(RangeInfo::new(range, SourceChange::from(edits))) 283 Ok(RangeInfo::new(range, SourceChange::from(edits)))
@@ -1086,6 +1106,95 @@ impl Foo {
1086} 1106}
1087"#, 1107"#,
1088 ); 1108 );
1109 check(
1110 "self",
1111 r#"
1112struct Foo { i: i32 }
1113
1114impl Foo {
1115 fn f(foo<|>: Foo) -> i32 {
1116 foo.i
1117 }
1118}
1119"#,
1120 r#"
1121struct Foo { i: i32 }
1122
1123impl Foo {
1124 fn f(self) -> i32 {
1125 self.i
1126 }
1127}
1128"#,
1129 );
1130 }
1131
1132 #[test]
1133 fn test_parameter_to_self_error_no_impl() {
1134 check(
1135 "self",
1136 r#"
1137struct Foo { i: i32 }
1138
1139fn f(foo<|>: &mut Foo) -> i32 {
1140 foo.i
1141}
1142"#,
1143 "error: No impl block found for function",
1144 );
1145 check(
1146 "self",
1147 r#"
1148struct Foo { i: i32 }
1149struct Bar;
1150
1151impl Bar {
1152 fn f(foo<|>: &mut Foo) -> i32 {
1153 foo.i
1154 }
1155}
1156"#,
1157 "error: Parameter type differs from impl block type",
1158 );
1159 }
1160
1161 #[test]
1162 fn test_parameter_to_self_error_not_first() {
1163 check(
1164 "self",
1165 r#"
1166struct Foo { i: i32 }
1167impl Foo {
1168 fn f(x: (), foo<|>: &mut Foo) -> i32 {
1169 foo.i
1170 }
1171}
1172"#,
1173 "error: Only the first parameter can be self",
1174 );
1175 }
1176
1177 #[test]
1178 fn test_parameter_to_self_impl_ref() {
1179 check(
1180 "self",
1181 r#"
1182struct Foo { i: i32 }
1183impl &Foo {
1184 fn f(foo<|>: &Foo) -> i32 {
1185 foo.i
1186 }
1187}
1188"#,
1189 r#"
1190struct Foo { i: i32 }
1191impl &Foo {
1192 fn f(self) -> i32 {
1193 self.i
1194 }
1195}
1196"#,
1197 );
1089 } 1198 }
1090 1199
1091 #[test] 1200 #[test]