Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide_diagnostics/src/handlers/missing_fields.rs')
| -rw-r--r-- | crates/ide_diagnostics/src/handlers/missing_fields.rs | 195 |
1 files changed, 135 insertions, 60 deletions
diff --git a/crates/ide_diagnostics/src/handlers/missing_fields.rs b/crates/ide_diagnostics/src/handlers/missing_fields.rs index 414878b230..6b70db9bfd 100644 --- a/crates/ide_diagnostics/src/handlers/missing_fields.rs +++ b/crates/ide_diagnostics/src/handlers/missing_fields.rs @@ -9,7 +9,7 @@ use stdx::format_to; use syntax::{ algo, ast::{self, make}, - AstNode, SyntaxNodePtr, + AstNode, SyntaxNode, SyntaxNodePtr, }; use text_edit::TextEdit; @@ -55,70 +55,95 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Ass } let root = ctx.sema.db.parse_or_expand(d.file)?; - let field_list_parent = match &d.field_list_parent { - Either::Left(record_expr) => record_expr.to_node(&root), - // FIXE: patterns should be fixable as well. - Either::Right(_) => return None, - }; - let old_field_list = field_list_parent.record_expr_field_list()?; - let new_field_list = old_field_list.clone_for_update(); - let mut locals = FxHashMap::default(); - ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| { - if let hir::ScopeDef::Local(local) = def { - locals.insert(name, local); - } - }); - let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent); - - let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default { - crate::ExprFillDefaultMode::Todo => Some(make::ext::expr_todo()), - crate::ExprFillDefaultMode::Default => { - let default_constr = get_default_constructor(ctx, d, ty); - match default_constr { - Some(default_constr) => Some(default_constr), - _ => Some(make::ext::expr_todo()), + let build_text_edit = |parent_syntax, new_syntax: &SyntaxNode, old_syntax| { + let edit = { + let mut builder = TextEdit::builder(); + if d.file.is_macro() { + // we can't map the diff up into the macro input unfortunately, as the macro loses all + // whitespace information so the diff wouldn't be applicable no matter what + // This has the downside that the cursor will be moved in macros by doing it without a diff + // but that is a trade off we can make. + // FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here + let range = ctx.sema.original_range_opt(old_syntax)?; + builder.replace(range.range, new_syntax.to_string()); + } else { + algo::diff(old_syntax, new_syntax).into_text_edit(&mut builder); } - } + builder.finish() + }; + Some(vec![fix( + "fill_missing_fields", + "Fill struct fields", + SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit), + ctx.sema.original_range(parent_syntax).range, + )]) }; - for (f, ty) in missing_fields.iter() { - let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) { - cov_mark::hit!(field_shorthand); - let candidate_ty = local_candidate.ty(ctx.sema.db); - if ty.could_unify_with(ctx.sema.db, &candidate_ty) { - None - } else { - generate_fill_expr(ty) + match &d.field_list_parent { + Either::Left(record_expr) => { + let field_list_parent = record_expr.to_node(&root); + let missing_fields = ctx.sema.record_literal_missing_fields(&field_list_parent); + + let mut locals = FxHashMap::default(); + ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| { + if let hir::ScopeDef::Local(local) = def { + locals.insert(name, local); + } + }); + + let generate_fill_expr = |ty: &Type| match ctx.config.expr_fill_default { + crate::ExprFillDefaultMode::Todo => make::ext::expr_todo(), + crate::ExprFillDefaultMode::Default => { + get_default_constructor(ctx, d, ty).unwrap_or_else(|| make::ext::expr_todo()) + } + }; + + let old_field_list = field_list_parent.record_expr_field_list()?; + let new_field_list = old_field_list.clone_for_update(); + for (f, ty) in missing_fields.iter() { + let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) { + cov_mark::hit!(field_shorthand); + let candidate_ty = local_candidate.ty(ctx.sema.db); + if ty.could_unify_with(ctx.sema.db, &candidate_ty) { + None + } else { + Some(generate_fill_expr(ty)) + } + } else { + Some(generate_fill_expr(ty)) + }; + let field = make::record_expr_field( + make::name_ref(&f.name(ctx.sema.db).to_smol_str()), + field_expr, + ); + new_field_list.add_field(field.clone_for_update()); } - } else { - generate_fill_expr(ty) - }; - let field = - make::record_expr_field(make::name_ref(&f.name(ctx.sema.db).to_smol_str()), field_expr) - .clone_for_update(); - new_field_list.add_field(field); - } - - let mut builder = TextEdit::builder(); - if d.file.is_macro() { - // we can't map the diff up into the macro input unfortunately, as the macro loses all - // whitespace information so the diff wouldn't be applicable no matter what - // This has the downside that the cursor will be moved in macros by doing it without a diff - // but that is a trade off we can make. - // FIXE: this also currently discards a lot of whitespace in the input... we really need a formatter here - let range = ctx.sema.original_range_opt(old_field_list.syntax())?; - builder.replace(range.range, new_field_list.to_string()); - } else { - algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder); + build_text_edit( + field_list_parent.syntax(), + new_field_list.syntax(), + old_field_list.syntax(), + ) + } + Either::Right(record_pat) => { + let field_list_parent = record_pat.to_node(&root); + let missing_fields = ctx.sema.record_pattern_missing_fields(&field_list_parent); + + let old_field_list = field_list_parent.record_pat_field_list()?; + let new_field_list = old_field_list.clone_for_update(); + for (f, _) in missing_fields.iter() { + let field = make::record_pat_field_shorthand(make::name_ref( + &f.name(ctx.sema.db).to_smol_str(), + )); + new_field_list.add_field(field.clone_for_update()); + } + build_text_edit( + field_list_parent.syntax(), + new_field_list.syntax(), + old_field_list.syntax(), + ) + } } - let edit = builder.finish(); - Some(vec![fix( - "fill_missing_fields", - "Fill struct fields", - SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit), - ctx.sema.original_range(field_list_parent.syntax()).range, - )]) } fn make_ty(ty: &hir::Type, db: &dyn HirDatabase, module: hir::Module) -> ast::Type { @@ -190,7 +215,7 @@ mod tests { struct S { foo: i32, bar: () } fn baz(s: S) { let S { foo: _ } = s; - //^ error: missing structure fields: + //^ 💡 error: missing structure fields: //| - bar } "#, @@ -582,6 +607,56 @@ fn f() { } #[test] + fn test_fill_struct_pat_fields() { + check_fix( + r#" +struct S { a: &'static str, b: i32 } + +fn f() { + let S { + $0 + }; +} +"#, + r#" +struct S { a: &'static str, b: i32 } + +fn f() { + let S { + a, + b, + }; +} +"#, + ); + } + + #[test] + fn test_fill_struct_pat_fields_partial() { + check_fix( + r#" +struct S { a: &'static str, b: i32 } + +fn f() { + let S { + a,$0 + }; +} +"#, + r#" +struct S { a: &'static str, b: i32 } + +fn f() { + let S { + a, + b, + }; +} +"#, + ); + } + + #[test] fn import_extern_crate_clash_with_inner_item() { // This is more of a resolver test, but doesn't really work with the hir_def testsuite. |