1use hir::{ModPath, ModuleDef};
2use ide_db::{FileId, RootDatabase, famous_defs::FamousDefs};
3use syntax::{
4 Edition,
5 ast::{self, AstNode, HasName, edit::AstNodeEdit},
6 syntax_editor::Position,
7};
8
9use crate::{
10 AssistId,
11 assist_context::{AssistContext, Assists, SourceChangeBuilder},
12 utils::generate_trait_impl_intransitive_with_item,
13};
14
15pub(crate) fn generate_deref(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
42 generate_record_deref(acc, ctx).or_else(|| generate_tuple_deref(acc, ctx))
43}
44
45fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
46 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
47 let field = ctx.find_node_at_offset::<ast::RecordField>()?;
48
49 let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
50 None => DerefType::Deref,
51 Some(DerefType::Deref) => DerefType::DerefMut,
52 Some(DerefType::DerefMut) => {
53 cov_mark::hit!(test_add_record_deref_impl_already_exists);
54 return None;
55 }
56 };
57
58 let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
59 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.db())));
60 let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate(ctx.db()))?;
61 let trait_path = module.find_path(ctx.db(), ModuleDef::Trait(trait_), cfg)?;
62
63 let field_type = field.ty()?;
64 let field_name = field.name()?;
65 let target = field.syntax().text_range();
66 let file_id = ctx.vfs_file_id();
67 acc.add(
68 AssistId::generate("generate_deref"),
69 format!("Generate `{deref_type_to_generate:?}` impl using `{field_name}`"),
70 target,
71 |edit| {
72 generate_edit(
73 ctx.db(),
74 edit,
75 file_id,
76 strukt,
77 field_type,
78 &field_name.to_string(),
79 deref_type_to_generate,
80 trait_path,
81 module.krate(ctx.db()).edition(ctx.db()),
82 )
83 },
84 )
85}
86
87fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
88 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
89 let field = ctx.find_node_at_offset::<ast::TupleField>()?;
90 let field_list = ctx.find_node_at_offset::<ast::TupleFieldList>()?;
91 let field_list_index = field_list.syntax().children().position(|s| &s == field.syntax())?;
92
93 let deref_type_to_generate = match existing_deref_impl(&ctx.sema, &strukt) {
94 None => DerefType::Deref,
95 Some(DerefType::Deref) => DerefType::DerefMut,
96 Some(DerefType::DerefMut) => {
97 cov_mark::hit!(test_add_field_deref_impl_already_exists);
98 return None;
99 }
100 };
101
102 let module = ctx.sema.to_def(&strukt)?.module(ctx.db());
103 let cfg = ctx.config.find_path_config(ctx.sema.is_nightly(module.krate(ctx.sema.db)));
104 let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate(ctx.db()))?;
105 let trait_path = module.find_path(ctx.db(), ModuleDef::Trait(trait_), cfg)?;
106
107 let field_type = field.ty()?;
108 let target = field.syntax().text_range();
109 let file_id = ctx.vfs_file_id();
110 acc.add(
111 AssistId::generate("generate_deref"),
112 format!("Generate `{deref_type_to_generate:?}` impl using `{field}`"),
113 target,
114 |edit| {
115 generate_edit(
116 ctx.db(),
117 edit,
118 file_id,
119 strukt,
120 field_type,
121 &field_list_index.to_string(),
122 deref_type_to_generate,
123 trait_path,
124 module.krate(ctx.db()).edition(ctx.db()),
125 )
126 },
127 )
128}
129
130fn generate_edit(
131 db: &RootDatabase,
132 edit: &mut SourceChangeBuilder,
133 file_id: FileId,
134 strukt: ast::Struct,
135 field_type: ast::Type,
136 field_name: &str,
137 deref_type: DerefType,
138 trait_path: ModPath,
139 edition: Edition,
140) {
141 let editor = edit.make_editor(strukt.syntax());
142 let make = editor.make();
143 let strukt_adt = ast::Adt::Struct(strukt.clone());
144 let trait_ty = make.ty(&trait_path.display(db, edition).to_string());
145
146 let assoc_items: Vec<ast::AssocItem> = match deref_type {
147 DerefType::Deref => {
148 let target_alias =
149 make.ty_alias([], "Target", None, None, None, Some((field_type, None)));
150 let ret_ty =
151 make.ty_ref(make.ty_path(make.path_from_text("Self::Target")).into(), false);
152 let field_expr = make.expr_field(make.expr_path(make.ident_path("self")), field_name);
153 let body = make.block_expr([], Some(make.expr_ref(field_expr.into(), false)));
154 let fn_ = make
155 .fn_(
156 [],
157 None,
158 make.name("deref"),
159 None,
160 None,
161 make.param_list(Some(make.self_param()), []),
162 body,
163 Some(make.ret_type(ret_ty)),
164 false,
165 false,
166 false,
167 false,
168 )
169 .indent(1.into());
170 vec![ast::AssocItem::TypeAlias(target_alias), ast::AssocItem::Fn(fn_)]
171 }
172 DerefType::DerefMut => {
173 let ret_ty =
174 make.ty_ref(make.ty_path(make.path_from_text("Self::Target")).into(), true);
175 let field_expr = make.expr_field(make.expr_path(make.ident_path("self")), field_name);
176 let body = make.block_expr([], Some(make.expr_ref(field_expr.into(), true)));
177 let fn_ = make
178 .fn_(
179 [],
180 None,
181 make.name("deref_mut"),
182 None,
183 None,
184 make.param_list(Some(make.mut_self_param()), []),
185 body,
186 Some(make.ret_type(ret_ty)),
187 false,
188 false,
189 false,
190 false,
191 )
192 .indent(1.into());
193 vec![ast::AssocItem::Fn(fn_)]
194 }
195 };
196
197 let body = make.assoc_item_list(assoc_items);
198 let indent = strukt.indent_level();
199 let impl_ = generate_trait_impl_intransitive_with_item(make, &strukt_adt, trait_ty, body)
200 .indent(indent);
201 editor.insert_all(
202 Position::after(strukt.syntax()),
203 vec![make.whitespace(&format!("\n\n{indent}")).into(), impl_.syntax().clone().into()],
204 );
205 edit.add_file_edits(file_id, editor);
206}
207
208fn existing_deref_impl(
209 sema: &hir::Semantics<'_, RootDatabase>,
210 strukt: &ast::Struct,
211) -> Option<DerefType> {
212 let strukt = sema.to_def(strukt)?;
213 let krate = strukt.module(sema.db).krate(sema.db);
214
215 let deref_trait = FamousDefs(sema, krate).core_ops_Deref()?;
216 let deref_mut_trait = FamousDefs(sema, krate).core_ops_DerefMut()?;
217 let strukt_type = strukt.ty(sema.db);
218
219 if strukt_type.impls_trait(sema.db, deref_trait, &[]) {
220 if strukt_type.impls_trait(sema.db, deref_mut_trait, &[]) {
221 Some(DerefType::DerefMut)
222 } else {
223 Some(DerefType::Deref)
224 }
225 } else {
226 None
227 }
228}
229
230#[derive(Debug)]
231enum DerefType {
232 Deref,
233 DerefMut,
234}
235
236impl DerefType {
237 fn to_trait(
238 &self,
239 sema: &hir::Semantics<'_, RootDatabase>,
240 krate: hir::Crate,
241 ) -> Option<hir::Trait> {
242 match self {
243 DerefType::Deref => FamousDefs(sema, krate).core_ops_Deref(),
244 DerefType::DerefMut => FamousDefs(sema, krate).core_ops_DerefMut(),
245 }
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use crate::tests::{check_assist, check_assist_not_applicable};
252
253 use super::*;
254
255 #[test]
256 fn test_generate_record_deref() {
257 check_assist(
258 generate_deref,
259 r#"
260//- minicore: deref
261struct A { }
262struct B { $0a: A }"#,
263 r#"
264struct A { }
265struct B { a: A }
266
267impl core::ops::Deref for B {
268 type Target = A;
269
270 fn deref(&self) -> &Self::Target {
271 &self.a
272 }
273}"#,
274 );
275 }
276
277 #[test]
278 fn test_generate_record_deref_with_generic() {
279 check_assist(
280 generate_deref,
281 r#"
282//- minicore: deref
283struct A<T>($0T);
284"#,
285 r#"
286struct A<T>(T);
287
288impl<T> core::ops::Deref for A<T> {
289 type Target = T;
290
291 fn deref(&self) -> &Self::Target {
292 &self.0
293 }
294}
295"#,
296 );
297 }
298
299 #[test]
300 fn test_generate_record_deref_short_path() {
301 check_assist(
302 generate_deref,
303 r#"
304//- minicore: deref
305use core::ops::Deref;
306struct A { }
307struct B { $0a: A }"#,
308 r#"
309use core::ops::Deref;
310struct A { }
311struct B { a: A }
312
313impl Deref for B {
314 type Target = A;
315
316 fn deref(&self) -> &Self::Target {
317 &self.a
318 }
319}"#,
320 );
321 }
322
323 #[test]
324 fn test_generate_field_deref_idx_0() {
325 check_assist(
326 generate_deref,
327 r#"
328//- minicore: deref
329struct A { }
330struct B($0A);"#,
331 r#"
332struct A { }
333struct B(A);
334
335impl core::ops::Deref for B {
336 type Target = A;
337
338 fn deref(&self) -> &Self::Target {
339 &self.0
340 }
341}"#,
342 );
343 }
344 #[test]
345 fn test_generate_field_deref_idx_1() {
346 check_assist(
347 generate_deref,
348 r#"
349//- minicore: deref
350struct A { }
351struct B(u8, $0A);"#,
352 r#"
353struct A { }
354struct B(u8, A);
355
356impl core::ops::Deref for B {
357 type Target = A;
358
359 fn deref(&self) -> &Self::Target {
360 &self.1
361 }
362}"#,
363 );
364 }
365
366 #[test]
367 fn test_generates_derefmut_when_deref_present() {
368 check_assist(
369 generate_deref,
370 r#"
371//- minicore: deref, deref_mut
372struct B { $0a: u8 }
373
374impl core::ops::Deref for B {}
375"#,
376 r#"
377struct B { a: u8 }
378
379impl core::ops::DerefMut for B {
380 fn deref_mut(&mut self) -> &mut Self::Target {
381 &mut self.a
382 }
383}
384
385impl core::ops::Deref for B {}
386"#,
387 );
388 }
389
390 #[test]
391 fn test_generate_record_deref_not_applicable_if_already_impl() {
392 cov_mark::check!(test_add_record_deref_impl_already_exists);
393 check_assist_not_applicable(
394 generate_deref,
395 r#"
396//- minicore: deref, deref_mut
397struct A { }
398struct B { $0a: A }
399
400impl core::ops::Deref for B {}
401impl core::ops::DerefMut for B {}
402"#,
403 )
404 }
405
406 #[test]
407 fn test_generate_field_deref_not_applicable_if_already_impl() {
408 cov_mark::check!(test_add_field_deref_impl_already_exists);
409 check_assist_not_applicable(
410 generate_deref,
411 r#"
412//- minicore: deref, deref_mut
413struct A { }
414struct B($0A)
415
416impl core::ops::Deref for B {}
417impl core::ops::DerefMut for B {}
418"#,
419 )
420 }
421}