ide_diagnostics/handlers/
no_such_field.rsuse either::Either;
use hir::{db::ExpandDatabase, HasSource, HirDisplay, HirFileIdExt, Semantics, VariantId};
use ide_db::text_edit::TextEdit;
use ide_db::{source_change::SourceChange, EditionedFileId, RootDatabase};
use syntax::{
ast::{self, edit::IndentLevel, make},
AstNode,
};
use crate::{fix, Assist, Diagnostic, DiagnosticCode, DiagnosticsContext};
pub(crate) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
let node = d.field.map(Into::into);
if d.private {
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcHardError("E0451"),
"field is private",
node,
)
} else {
Diagnostic::new_with_syntax_node_ptr(
ctx,
match d.variant {
VariantId::EnumVariantId(_) => DiagnosticCode::RustcHardError("E0559"),
_ => DiagnosticCode::RustcHardError("E0560"),
},
"no such field",
node,
)
.with_fixes(fixes(ctx, d))
}
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
let root = ctx.sema.db.parse_or_expand(d.field.file_id);
match &d.field.value.to_node(&root) {
Either::Left(node) => missing_record_expr_field_fixes(
&ctx.sema,
d.field.file_id.original_file(ctx.sema.db),
node,
),
_ => None,
}
}
fn missing_record_expr_field_fixes(
sema: &Semantics<'_, RootDatabase>,
usage_file_id: EditionedFileId,
record_expr_field: &ast::RecordExprField,
) -> Option<Vec<Assist>> {
let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
let def_id = sema.resolve_variant(record_lit)?;
let module;
let def_file_id;
let record_fields = match def_id {
hir::VariantDef::Struct(s) => {
module = s.module(sema.db);
let source = s.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
hir::VariantDef::Union(u) => {
module = u.module(sema.db);
let source = u.source(sema.db)?;
def_file_id = source.file_id;
source.value.record_field_list()?
}
hir::VariantDef::Variant(e) => {
module = e.module(sema.db);
let source = e.source(sema.db)?;
def_file_id = source.file_id;
let fields = source.value.field_list()?;
record_field_list(fields)?
}
};
let def_file_id = def_file_id.original_file(sema.db);
let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?.adjusted();
if new_field_type.is_unknown() {
return None;
}
let new_field = make::record_field(
None,
make::name(record_expr_field.field_name()?.ident_token()?.text()),
make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?),
);
let last_field = record_fields.fields().last()?;
let last_field_syntax = last_field.syntax();
let indent = IndentLevel::from_node(last_field_syntax);
let mut new_field = new_field.to_string();
if usage_file_id != def_file_id {
new_field = format!("pub(crate) {new_field}");
}
new_field = format!("\n{indent}{new_field}");
let needs_comma = !last_field_syntax.to_string().ends_with(',');
if needs_comma {
new_field = format!(",{new_field}");
}
let source_change = SourceChange::from_text_edit(
def_file_id,
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
);
return Some(vec![fix(
"create_field",
"Create field",
source_change,
record_expr_field.syntax().text_range(),
)]);
fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
match field_def_list {
ast::FieldList::RecordFieldList(it) => Some(it),
ast::FieldList::TupleFieldList(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::tests::{check_diagnostics, check_fix, check_no_fix};
#[test]
fn dont_work_for_field_with_disabled_cfg() {
check_diagnostics(
r#"
struct Test {
#[cfg(feature = "hello")]
test: u32,
other: u32
}
fn main() {
let a = Test {
#[cfg(feature = "hello")]
test: 1,
other: 1
};
let Test {
#[cfg(feature = "hello")]
test,
mut other,
..
} = a;
other += 1;
}
"#,
);
}
#[test]
fn no_such_field_diagnostics() {
check_diagnostics(
r#"
struct S { foo: i32, bar: () }
impl S {
fn new(
s@S {
//^ 💡 error: missing structure fields:
//| - bar
foo,
baz: baz2,
//^^^^^^^^^ error: no such field
qux
//^^^ error: no such field
}: S
) -> S {
S {
//^ 💡 error: missing structure fields:
//| - bar
foo,
baz: baz2,
//^^^^^^^^^ error: no such field
qux
//^^^ error: no such field
} = s;
S {
//^ 💡 error: missing structure fields:
//| - bar
foo: 92,
baz: 62,
//^^^^^^^ 💡 error: no such field
qux
//^^^ error: no such field
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct MyStruct {
my_val: usize,
#[cfg(feature = "foo")]
bar: bool,
}
impl MyStruct {
#[cfg(feature = "foo")]
pub(crate) fn new(my_val: usize, bar: bool) -> Self {
Self { my_val, bar }
}
#[cfg(not(feature = "foo"))]
pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
Self { my_val }
}
}
"#,
);
}
#[test]
fn no_such_field_enum_with_feature_flag_diagnostics() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
enum Foo {
#[cfg(not(feature = "foo"))]
Buz,
#[cfg(feature = "foo")]
Bar,
Baz
}
fn test_fn(f: Foo) {
match f {
Foo::Bar => {},
Foo::Baz => {},
}
}
"#,
);
}
#[test]
fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
check_diagnostics(
r#"
//- /lib.rs crate:foo cfg:feature=foo
struct S {
#[cfg(feature = "foo")]
foo: u32,
#[cfg(not(feature = "foo"))]
bar: u32,
}
impl S {
#[cfg(feature = "foo")]
fn new(foo: u32) -> Self {
Self { foo }
}
#[cfg(not(feature = "foo"))]
fn new(bar: u32) -> Self {
Self { bar }
}
fn new2(bar: u32) -> Self {
#[cfg(feature = "foo")]
{ Self { foo: bar } }
#[cfg(not(feature = "foo"))]
{ Self { bar } }
}
fn new2(val: u32) -> Self {
Self {
#[cfg(feature = "foo")]
foo: val,
#[cfg(not(feature = "foo"))]
bar: val,
}
}
}
"#,
);
}
#[test]
fn no_such_field_with_type_macro() {
check_diagnostics(
r#"
macro_rules! Type { () => { u32 }; }
struct Foo { bar: Type![] }
impl Foo {
fn new() -> Self {
Foo { bar: 0 }
}
}
"#,
);
}
#[test]
fn test_add_field_from_usage() {
check_fix(
r"
fn main() {
Foo { bar: 3, baz$0: false};
}
struct Foo {
bar: i32
}
",
r"
fn main() {
Foo { bar: 3, baz: false};
}
struct Foo {
bar: i32,
baz: bool
}
",
)
}
#[test]
fn test_add_field_in_other_file_from_usage() {
check_fix(
r#"
//- /main.rs
mod foo;
fn main() {
foo::Foo { bar: 3, $0baz: false};
}
//- /foo.rs
pub struct Foo {
bar: i32
}
"#,
r#"
pub struct Foo {
bar: i32,
pub(crate) baz: bool
}
"#,
)
}
#[test]
fn test_tuple_field_on_record_struct() {
check_no_fix(
r#"
struct Struct {}
fn main() {
Struct {
0$0: 0
}
}
"#,
)
}
#[test]
fn test_struct_field_private() {
check_diagnostics(
r#"
mod m {
pub struct Struct {
field: u32,
field2: u32,
}
}
fn f(s@m::Struct {
field: f,
//^^^^^^^^ error: field is private
field2
//^^^^^^ error: field is private
}: m::Struct) {
// assignee expression
m::Struct {
field: 0,
//^^^^^^^^ error: field is private
field2
//^^^^^^ error: field is private
} = s;
m::Struct {
field: 0,
//^^^^^^^^ error: field is private
field2
//^^^^^^ error: field is private
};
}
"#,
)
}
}