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