ide_diagnostics/handlers/
private_field.rs1use hir::{EditionedFileId, FileRange, HasCrate, HasSource, Semantics};
2use ide_db::{RootDatabase, assists::Assist, source_change::SourceChange, text_edit::TextEdit};
3use syntax::{
4 AstNode, TextRange,
5 ast::{HasName, HasVisibility},
6};
7
8use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
9
10pub(crate) fn private_field(ctx: &DiagnosticsContext<'_>, d: &hir::PrivateField) -> Diagnostic {
14 Diagnostic::new_with_syntax_node_ptr(
15 ctx,
16 DiagnosticCode::RustcHardError("E0616"),
17 format!(
18 "field `{}` of `{}` is private",
19 d.field.name(ctx.sema.db).display(ctx.sema.db, ctx.edition),
20 d.field.parent_def(ctx.sema.db).name(ctx.sema.db).display(ctx.sema.db, ctx.edition)
21 ),
22 d.expr.map(|it| it.into()),
23 )
24 .stable()
25 .with_fixes(field_is_private_fixes(
26 &ctx.sema,
27 d.expr.file_id.original_file(ctx.sema.db),
28 d.field,
29 ctx.sema.original_range(d.expr.to_node(ctx.sema.db).syntax()).range,
30 ))
31}
32
33pub(crate) fn field_is_private_fixes(
34 sema: &Semantics<'_, RootDatabase>,
35 usage_file_id: EditionedFileId,
36 private_field: hir::Field,
37 fix_range: TextRange,
38) -> Option<Vec<Assist>> {
39 let def_crate = private_field.krate(sema.db);
40 let usage_crate = sema.file_to_module_def(usage_file_id.file_id(sema.db))?.krate(sema.db);
41 let mut visibility_text = if usage_crate == def_crate { "pub(crate) " } else { "pub " };
42
43 let source = private_field.source(sema.db)?;
44 let existing_visibility = match &source.value {
45 hir::FieldSource::Named(it) => it.visibility(),
46 hir::FieldSource::Pos(it) => it.visibility(),
47 };
48 let range = match existing_visibility {
49 Some(visibility) => {
50 visibility_text = visibility_text.trim_end();
52 source.with_value(visibility.syntax()).original_file_range_opt(sema.db)?.0
53 }
54 None => {
55 let (range, _) = source
56 .map(|it| {
57 Some(match it {
58 hir::FieldSource::Named(it) => {
59 it.unsafe_token().or(it.name()?.ident_token())?.text_range()
60 }
61 hir::FieldSource::Pos(it) => it.ty()?.syntax().text_range(),
62 })
63 })
64 .transpose()?
65 .original_node_file_range_opt(sema.db)?;
66
67 FileRange { file_id: range.file_id, range: TextRange::empty(range.range.start()) }
68 }
69 };
70 let source_change = SourceChange::from_text_edit(
71 range.file_id.file_id(sema.db),
72 TextEdit::replace(range.range, visibility_text.into()),
73 );
74
75 Some(vec![fix(
76 "increase_field_visibility",
77 "Increase field visibility",
78 source_change,
79 fix_range,
80 )])
81}
82
83#[cfg(test)]
84mod tests {
85 use crate::tests::{check_diagnostics, check_fix};
86
87 #[test]
88 fn private_field() {
89 check_diagnostics(
90 r#"
91mod module { pub struct Struct { field: u32 } }
92fn main(s: module::Struct) {
93 s.field;
94 //^^^^^^^ 💡 error: field `field` of `Struct` is private
95}
96"#,
97 );
98 }
99
100 #[test]
101 fn private_tuple_field() {
102 check_diagnostics(
103 r#"
104mod module { pub struct Struct(u32); }
105fn main(s: module::Struct) {
106 s.0;
107 //^^^ 💡 error: field `0` of `Struct` is private
108}
109"#,
110 );
111 }
112
113 #[test]
114 fn private_but_shadowed_in_deref() {
115 check_diagnostics(
116 r#"
117//- minicore: deref
118mod module {
119 pub struct Struct { field: Inner }
120 pub struct Inner { pub field: u32 }
121 impl core::ops::Deref for Struct {
122 type Target = Inner;
123 fn deref(&self) -> &Inner { &self.field }
124 }
125}
126fn main(s: module::Struct) {
127 s.field;
128}
129"#,
130 );
131 }
132
133 #[test]
134 fn block_module_madness() {
135 check_diagnostics(
136 r#"
137fn main() {
138 let strukt = {
139 use crate as ForceParentBlockDefMap;
140 {
141 pub struct Struct {
142 field: (),
143 }
144 Struct { field: () }
145 }
146 };
147 strukt.field;
148}
149"#,
150 );
151 }
152
153 #[test]
154 fn block_module_madness2() {
155 check_diagnostics(
156 r#"
157fn main() {
158 use crate as ForceParentBlockDefMap;
159 let strukt = {
160 use crate as ForceParentBlockDefMap;
161 {
162 pub struct Struct {
163 field: (),
164 }
165 {
166 use crate as ForceParentBlockDefMap;
167 {
168 Struct { field: () }
169 }
170 }
171 }
172 };
173 strukt.field;
174}
175"#,
176 );
177 }
178
179 #[test]
180 fn change_visibility_fix() {
181 check_fix(
182 r#"
183pub mod foo {
184 pub mod bar {
185 pub struct Struct {
186 field: i32,
187 }
188 }
189}
190
191fn foo(v: foo::bar::Struct) {
192 v.field$0;
193}
194 "#,
195 r#"
196pub mod foo {
197 pub mod bar {
198 pub struct Struct {
199 pub(crate) field: i32,
200 }
201 }
202}
203
204fn foo(v: foo::bar::Struct) {
205 v.field;
206}
207 "#,
208 );
209 }
210
211 #[test]
212 fn change_visibility_with_existing_visibility() {
213 check_fix(
214 r#"
215pub mod foo {
216 pub mod bar {
217 pub struct Struct {
218 pub(super) field: i32,
219 }
220 }
221}
222
223fn foo(v: foo::bar::Struct) {
224 v.field$0;
225}
226 "#,
227 r#"
228pub mod foo {
229 pub mod bar {
230 pub struct Struct {
231 pub(crate) field: i32,
232 }
233 }
234}
235
236fn foo(v: foo::bar::Struct) {
237 v.field;
238}
239 "#,
240 );
241 }
242
243 #[test]
244 fn change_visibility_of_field_with_doc_comment() {
245 check_fix(
246 r#"
247pub mod foo {
248 pub struct Foo {
249 /// This is a doc comment
250 bar: u32,
251 }
252}
253
254fn main() {
255 let x = foo::Foo { bar: 0 };
256 x.bar$0;
257}
258 "#,
259 r#"
260pub mod foo {
261 pub struct Foo {
262 /// This is a doc comment
263 pub(crate) bar: u32,
264 }
265}
266
267fn main() {
268 let x = foo::Foo { bar: 0 };
269 x.bar;
270}
271 "#,
272 );
273 }
274
275 #[test]
276 fn change_visibility_of_field_with_line_comment() {
277 check_fix(
278 r#"
279pub mod foo {
280 pub struct Foo {
281 // This is a line comment
282 bar: u32,
283 }
284}
285
286fn main() {
287 let x = foo::Foo { bar: 0 };
288 x.bar$0;
289}
290 "#,
291 r#"
292pub mod foo {
293 pub struct Foo {
294 // This is a line comment
295 pub(crate) bar: u32,
296 }
297}
298
299fn main() {
300 let x = foo::Foo { bar: 0 };
301 x.bar;
302}
303 "#,
304 );
305 }
306
307 #[test]
308 fn change_visibility_of_field_with_multiple_doc_comments() {
309 check_fix(
310 r#"
311pub mod foo {
312 pub struct Foo {
313 /// First line
314 /// Second line
315 bar: u32,
316 }
317}
318
319fn main() {
320 let x = foo::Foo { bar: 0 };
321 x.bar$0;
322}
323 "#,
324 r#"
325pub mod foo {
326 pub struct Foo {
327 /// First line
328 /// Second line
329 pub(crate) bar: u32,
330 }
331}
332
333fn main() {
334 let x = foo::Foo { bar: 0 };
335 x.bar;
336}
337 "#,
338 );
339 }
340
341 #[test]
342 fn change_visibility_of_field_with_attr_and_comment() {
343 check_fix(
344 r#"
345mod foo {
346 pub struct Foo {
347 #[rustfmt::skip]
348 /// First line
349 /// Second line
350 bar: u32,
351 }
352}
353fn main() {
354 foo::Foo { $0bar: 42 };
355}
356 "#,
357 r#"
358mod foo {
359 pub struct Foo {
360 #[rustfmt::skip]
361 /// First line
362 /// Second line
363 pub(crate) bar: u32,
364 }
365}
366fn main() {
367 foo::Foo { bar: 42 };
368}
369 "#,
370 );
371 }
372
373 #[test]
374 fn change_visibility_of_field_with_macro() {
375 check_fix(
376 r#"
377macro_rules! allow_unused {
378 ($vis:vis $struct:ident $name:ident { $($fvis:vis $field:ident : $ty:ty,)* }) => {
379 $vis $struct $name {
380 $(
381 #[allow(unused)]
382 $fvis $field : $ty,
383 )*
384 }
385 };
386}
387mod foo {
388 allow_unused!(
389 pub struct Foo {
390 x: i32,
391 }
392 );
393}
394fn main() {
395 let foo = foo::Foo { x: 2 };
396 let _ = foo.$0x
397}
398 "#,
399 r#"
400macro_rules! allow_unused {
401 ($vis:vis $struct:ident $name:ident { $($fvis:vis $field:ident : $ty:ty,)* }) => {
402 $vis $struct $name {
403 $(
404 #[allow(unused)]
405 $fvis $field : $ty,
406 )*
407 }
408 };
409}
410mod foo {
411 allow_unused!(
412 pub struct Foo {
413 pub(crate) x: i32,
414 }
415 );
416}
417fn main() {
418 let foo = foo::Foo { x: 2 };
419 let _ = foo.x
420}
421 "#,
422 );
423 }
424}