Skip to content

Commit 20273d2

Browse files
committed
Add a quickfix for accessing a private field of a struct
1 parent f98b622 commit 20273d2

File tree

1 file changed

+131
-9
lines changed

1 file changed

+131
-9
lines changed

crates/ide-diagnostics/src/handlers/no_such_field.rs

Lines changed: 131 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use ide_db::text_edit::TextEdit;
44
use ide_db::{EditionedFileId, RootDatabase, source_change::SourceChange};
55
use syntax::{
66
AstNode,
7-
ast::{self, edit::IndentLevel, make},
7+
ast::{self, HasName, RecordField, RecordFieldList, edit::IndentLevel, make},
88
};
99

1010
use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
@@ -23,6 +23,7 @@ pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField)
2323
node,
2424
)
2525
.stable()
26+
.with_fixes(field_is_private_fixes(ctx, d))
2627
} else {
2728
Diagnostic::new_with_syntax_node_ptr(
2829
ctx,
@@ -34,11 +35,87 @@ pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField)
3435
node,
3536
)
3637
.stable()
37-
.with_fixes(fixes(ctx, d))
38+
.with_fixes(no_such_field_fixes(ctx, d))
3839
}
3940
}
4041

41-
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
42+
fn field_is_private_fixes(
43+
ctx: &DiagnosticsContext<'_>,
44+
d: &hir::NoSuchField,
45+
) -> Option<Vec<Assist>> {
46+
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
47+
match &d.field.value.to_node(&root) {
48+
Either::Left(node) => private_record_expr_field_fixes(
49+
&ctx.sema,
50+
d.field.file_id.original_file(ctx.sema.db),
51+
node,
52+
),
53+
_ => None,
54+
}
55+
}
56+
fn private_record_expr_field_fixes(
57+
sema: &Semantics<'_, RootDatabase>,
58+
usage_file_id: EditionedFileId,
59+
record_expr_field: &ast::RecordExprField,
60+
) -> Option<Vec<Assist>> {
61+
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
62+
let def_id = sema.resolve_variant(record_lit)?;
63+
let module;
64+
let def_file_id;
65+
let record_fields = match def_id {
66+
hir::VariantDef::Struct(s) => {
67+
module = s.module(sema.db);
68+
let source = s.source(sema.db)?;
69+
def_file_id = source.file_id;
70+
let fields = source.value.field_list()?;
71+
record_field_list(fields)?
72+
}
73+
hir::VariantDef::Union(u) => {
74+
module = u.module(sema.db);
75+
let source = u.source(sema.db)?;
76+
def_file_id = source.file_id;
77+
source.value.record_field_list()?
78+
}
79+
hir::VariantDef::Variant(e) => {
80+
module = e.module(sema.db);
81+
let source = e.source(sema.db)?;
82+
def_file_id = source.file_id;
83+
let fields = source.value.field_list()?;
84+
record_field_list(fields)?
85+
}
86+
};
87+
let def_file_id = def_file_id.original_file(sema.db);
88+
89+
let field_definition = find_field(&record_fields, record_expr_field)?;
90+
91+
let source_change = SourceChange::from_text_edit(
92+
def_file_id.file_id(sema.db),
93+
TextEdit::insert(field_definition.syntax().text_range().start(), "pub ".into()),
94+
);
95+
96+
return Some(vec![fix(
97+
"make_field_public",
98+
"Make field public",
99+
source_change,
100+
record_expr_field.syntax().text_range(),
101+
)]);
102+
}
103+
104+
fn find_field(
105+
field_list: &RecordFieldList,
106+
record_expr_field: &ast::RecordExprField,
107+
) -> Option<RecordField> {
108+
for field in field_list.fields() {
109+
let name = field.name()?;
110+
let token = name.ident_token()?;
111+
if token.text() == record_expr_field.field_name()?.ident_token()?.text() {
112+
return Some(field);
113+
}
114+
}
115+
None
116+
}
117+
118+
fn no_such_field_fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
42119
// FIXME: quickfix for pattern
43120
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
44121
match &d.field.value.to_node(&root) {
@@ -120,12 +197,11 @@ fn missing_record_expr_field_fixes(
120197
source_change,
121198
record_expr_field.syntax().text_range(),
122199
)]);
123-
124-
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
125-
match field_def_list {
126-
ast::FieldList::RecordFieldList(it) => Some(it),
127-
ast::FieldList::TupleFieldList(_) => None,
128-
}
200+
}
201+
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
202+
match field_def_list {
203+
ast::FieldList::RecordFieldList(it) => Some(it),
204+
ast::FieldList::TupleFieldList(_) => None,
129205
}
130206
}
131207

@@ -368,6 +444,52 @@ fn main() {
368444
)
369445
}
370446

447+
#[test]
448+
fn test_struct_field_private_fix() {
449+
check_diagnostics(
450+
r#"
451+
mod m {
452+
pub struct Struct {
453+
field: u32,
454+
}
455+
}
456+
fn f() {
457+
let _ = m::Struct {
458+
field: 0,
459+
//^^^^^^^^ 💡 error: field is private
460+
};
461+
}
462+
"#,
463+
);
464+
465+
check_fix(
466+
r#"
467+
mod m {
468+
pub struct Struct {
469+
field: u32,
470+
}
471+
}
472+
fn f() {
473+
let _ = m::Struct {
474+
field$0: 0,
475+
};
476+
}
477+
"#,
478+
r#"
479+
mod m {
480+
pub struct Struct {
481+
pub field: u32,
482+
}
483+
}
484+
fn f() {
485+
let _ = m::Struct {
486+
field: 0,
487+
};
488+
}
489+
"#,
490+
)
491+
}
492+
371493
#[test]
372494
fn test_struct_field_private() {
373495
check_diagnostics(

0 commit comments

Comments
 (0)