ide_diagnostics/handlers/
private_field.rs

1use 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
10// Diagnostic: private-field
11//
12// This diagnostic is triggered if the accessed field is not visible from the current module.
13pub(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            // If there is an existing visibility, don't insert whitespace after.
51            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}