Skip to content

Commit 7789e92

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

File tree

1 file changed

+126
-13
lines changed

1 file changed

+126
-13
lines changed

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

Lines changed: 126 additions & 13 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,78 @@ 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(&ctx.sema, node),
49+
_ => None,
50+
}
51+
}
52+
fn private_record_expr_field_fixes(
53+
sema: &Semantics<'_, RootDatabase>,
54+
record_expr_field: &ast::RecordExprField,
55+
) -> Option<Vec<Assist>> {
56+
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
57+
let def_id = sema.resolve_variant(record_lit)?;
58+
let def_file_id;
59+
let record_fields = match def_id {
60+
hir::VariantDef::Struct(s) => {
61+
let source = s.source(sema.db)?;
62+
def_file_id = source.file_id;
63+
let fields = source.value.field_list()?;
64+
record_field_list(fields)?
65+
}
66+
hir::VariantDef::Union(u) => {
67+
let source = u.source(sema.db)?;
68+
def_file_id = source.file_id;
69+
source.value.record_field_list()?
70+
}
71+
hir::VariantDef::Variant(e) => {
72+
let source = e.source(sema.db)?;
73+
def_file_id = source.file_id;
74+
let fields = source.value.field_list()?;
75+
record_field_list(fields)?
76+
}
77+
};
78+
let def_file_id = def_file_id.original_file(sema.db);
79+
80+
let field_definition = find_field(&record_fields, record_expr_field)?;
81+
82+
let source_change = SourceChange::from_text_edit(
83+
def_file_id.file_id(sema.db),
84+
TextEdit::insert(field_definition.syntax().text_range().start(), "pub ".into()),
85+
);
86+
87+
Some(vec![fix(
88+
"make_field_public",
89+
"Make field public",
90+
source_change,
91+
record_expr_field.syntax().text_range(),
92+
)])
93+
}
94+
95+
fn find_field(
96+
field_list: &RecordFieldList,
97+
record_expr_field: &ast::RecordExprField,
98+
) -> Option<RecordField> {
99+
for field in field_list.fields() {
100+
let name = field.name()?;
101+
let token = name.ident_token()?;
102+
if token.text() == record_expr_field.field_name()?.ident_token()?.text() {
103+
return Some(field);
104+
}
105+
}
106+
None
107+
}
108+
109+
fn no_such_field_fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
42110
// FIXME: quickfix for pattern
43111
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
44112
match &d.field.value.to_node(&root) {
@@ -120,12 +188,11 @@ fn missing_record_expr_field_fixes(
120188
source_change,
121189
record_expr_field.syntax().text_range(),
122190
)]);
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-
}
191+
}
192+
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
193+
match field_def_list {
194+
ast::FieldList::RecordFieldList(it) => Some(it),
195+
ast::FieldList::TupleFieldList(_) => None,
129196
}
130197
}
131198

@@ -368,6 +435,52 @@ fn main() {
368435
)
369436
}
370437

438+
#[test]
439+
fn test_struct_field_private_fix() {
440+
check_diagnostics(
441+
r#"
442+
mod m {
443+
pub struct Struct {
444+
field: u32,
445+
}
446+
}
447+
fn f() {
448+
let _ = m::Struct {
449+
field: 0,
450+
//^^^^^^^^ 💡 error: field is private
451+
};
452+
}
453+
"#,
454+
);
455+
456+
check_fix(
457+
r#"
458+
mod m {
459+
pub struct Struct {
460+
field: u32,
461+
}
462+
}
463+
fn f() {
464+
let _ = m::Struct {
465+
field$0: 0,
466+
};
467+
}
468+
"#,
469+
r#"
470+
mod m {
471+
pub struct Struct {
472+
pub field: u32,
473+
}
474+
}
475+
fn f() {
476+
let _ = m::Struct {
477+
field: 0,
478+
};
479+
}
480+
"#,
481+
)
482+
}
483+
371484
#[test]
372485
fn test_struct_field_private() {
373486
check_diagnostics(
@@ -387,15 +500,15 @@ fn f(s@m::Struct {
387500
// assignee expression
388501
m::Struct {
389502
field: 0,
390-
//^^^^^^^^ error: field is private
503+
//^^^^^^^^ 💡 error: field is private
391504
field2
392-
//^^^^^^ error: field is private
505+
//^^^^^^ 💡 error: field is private
393506
} = s;
394507
m::Struct {
395508
field: 0,
396-
//^^^^^^^^ error: field is private
509+
//^^^^^^^^ 💡 error: field is private
397510
field2
398-
//^^^^^^ error: field is private
511+
//^^^^^^ 💡 error: field is private
399512
};
400513
}
401514
"#,

0 commit comments

Comments
 (0)