1use either::Either;2use hir::{3 AssocItem, FindPathConfig, HasVisibility, HirDisplay, InFile, Type,4 db::{ExpandDatabase, HirDatabase},5 sym,6};7use ide_db::{8 FxHashMap,9 assists::{Assist, ExprFillDefaultMode},10 famous_defs::FamousDefs,11 imports::import_assets::item_for_path_search,12 source_change::SourceChange,13 syntax_helpers::tree_diff::diff,14 text_edit::TextEdit,15 use_trivial_constructor::use_trivial_constructor,16};17use stdx::format_to;18use syntax::{19 AstNode, Edition, SyntaxNode, SyntaxNodePtr, ToSmolStr,20 ast::{self, make},21};2223use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext, fix};2425// Diagnostic: missing-fields26//27// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.28//29// Example:30//31// ```rust32// struct A { a: u8, b: u8 }33//34// let a = A { a: 10 };35// ```36pub(crate) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {37 let mut message = String::from("missing structure fields:\n");38 for (field, _) in &d.missed_fields {39 format_to!(message, "- {}\n", field.display(ctx.sema.db, ctx.edition));40 }4142 let ptr = InFile::new(43 d.file,44 d.field_list_parent_path45 .map(SyntaxNodePtr::from)46 .unwrap_or_else(|| d.field_list_parent.into()),47 );4849 Diagnostic::new_with_syntax_node_ptr(ctx, DiagnosticCode::RustcHardError("E0063"), message, ptr)50 .stable()51 .with_fixes(fixes(ctx, d))52}5354fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {55 // Note that although we could add a diagnostics to56 // fill the missing tuple field, e.g :57 // `struct A(usize);`58 // `let a = A { 0: () }`59 // but it is uncommon usage and it should not be encouraged.60 if d.missed_fields.iter().any(|(name, _)| name.as_tuple_index().is_some()) {61 return None;62 }6364 let root = ctx.sema.db.parse_or_expand(d.file);6566 let current_module =67 ctx.sema.scope(d.field_list_parent.to_node(&root).syntax()).map(|it| it.module());68 let range = InFile::new(d.file, d.field_list_parent.text_range())69 .original_node_file_range_rooted_opt(ctx.sema.db)?;7071 if let Some(current_module) = current_module72 && d.missed_fields.iter().any(|(_, field)| !field.is_visible_from(ctx.db(), current_module))73 {74 return None;75 }7677 let build_text_edit = |new_syntax: &SyntaxNode, old_syntax| {78 let edit = {79 let old_range = ctx.sema.original_range_opt(old_syntax)?;80 if old_range.file_id != range.file_id {81 return None;82 }83 let mut builder = TextEdit::builder();84 if d.file.is_macro() {85 // we can't map the diff up into the macro input unfortunately, as the macro loses all86 // whitespace information so the diff wouldn't be applicable no matter what87 // This has the downside that the cursor will be moved in macros by doing it without a diff88 // but that is a trade off we can make.89 // FIXME: this also currently discards a lot of whitespace in the input... we really need a formatter here90 builder.replace(old_range.range, new_syntax.to_string());91 } else {92 diff(old_syntax, new_syntax).into_text_edit(&mut builder);93 }94 builder.finish()95 };96 Some(vec![fix(97 "fill_missing_fields",98 "Fill struct fields",99 SourceChange::from_text_edit(range.file_id.file_id(ctx.sema.db), edit),100 range.range,101 )])102 };103104 match &d.field_list_parent.to_node(&root) {105 Either::Left(field_list_parent) => {106 let missing_fields = ctx.sema.record_literal_missing_fields(field_list_parent);107108 let mut locals = FxHashMap::default();109 ctx.sema.scope(field_list_parent.syntax())?.process_all_names(&mut |name, def| {110 if let hir::ScopeDef::Local(local) = def {111 locals.insert(name, local);112 }113 });114115 let generate_fill_expr = |ty: &Type<'_>| match ctx.config.expr_fill_default {116 ExprFillDefaultMode::Todo => make::ext::expr_todo(),117 ExprFillDefaultMode::Underscore => make::ext::expr_underscore(),118 ExprFillDefaultMode::Default => {119 get_default_constructor(ctx, d, ty).unwrap_or_else(make::ext::expr_todo)120 }121 };122123 let old_field_list = field_list_parent.record_expr_field_list()?;124 let new_field_list = old_field_list.clone_for_update();125 for (f, ty) in missing_fields.iter() {126 let field_expr = if let Some(local_candidate) = locals.get(&f.name(ctx.sema.db)) {127 cov_mark::hit!(field_shorthand);128 let candidate_ty = local_candidate.ty(ctx.sema.db);129 if candidate_ty.could_coerce_to(ctx.sema.db, ty) {130 None131 } else {132 Some(generate_fill_expr(ty))133 }134 } else {135 let expr = (|| -> Option<ast::Expr> {136 let item_in_ns = hir::ItemInNs::from(hir::ModuleDef::from(ty.as_adt()?));137138 let type_path = current_module?.find_path(139 ctx.sema.db,140 item_for_path_search(ctx.sema.db, item_in_ns)?,141 FindPathConfig {142 prefer_no_std: ctx.config.prefer_no_std,143 prefer_prelude: ctx.config.prefer_prelude,144 prefer_absolute: ctx.config.prefer_absolute,145 allow_unstable: ctx.is_nightly,146 },147 )?;148149 use_trivial_constructor(150 ctx.sema.db,151 ide_db::helpers::mod_path_to_ast(&type_path, ctx.edition),152 ty,153 ctx.edition,154 )155 })();156157 if expr.is_some() { expr } else { Some(generate_fill_expr(ty)) }158 };159 let field = make::record_expr_field(160 make::name_ref(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),161 field_expr,162 );163 new_field_list.add_field(field.clone_for_update());164 }165 build_text_edit(new_field_list.syntax(), old_field_list.syntax())166 }167 Either::Right(field_list_parent) => {168 let missing_fields = ctx.sema.record_pattern_missing_fields(field_list_parent);169170 let old_field_list = field_list_parent.record_pat_field_list()?;171 let new_field_list = old_field_list.clone_for_update();172 for (f, _) in missing_fields.iter() {173 let field = make::record_pat_field_shorthand(174 make::ident_pat(175 false,176 false,177 make::name(&f.name(ctx.sema.db).display_no_db(ctx.edition).to_smolstr()),178 )179 .into(),180 );181 new_field_list.add_field(field.clone_for_update());182 }183 build_text_edit(new_field_list.syntax(), old_field_list.syntax())184 }185 }186}187188fn make_ty(189 ty: &hir::Type<'_>,190 db: &dyn HirDatabase,191 module: hir::Module,192 edition: Edition,193) -> ast::Type {194 let ty_str = match ty.as_adt() {195 Some(adt) => adt.name(db).display(db, edition).to_string(),196 None => {197 ty.display_source_code(db, module.into(), false).ok().unwrap_or_else(|| "_".to_owned())198 }199 };200201 make::ty(&ty_str)202}203204fn get_default_constructor(205 ctx: &DiagnosticsContext<'_>,206 d: &hir::MissingFields,207 ty: &Type<'_>,208) -> Option<ast::Expr> {209 if let Some(builtin_ty) = ty.as_builtin() {210 if builtin_ty.is_int() || builtin_ty.is_uint() {211 return Some(make::ext::zero_number());212 }213 if builtin_ty.is_float() {214 return Some(make::ext::zero_float());215 }216 if builtin_ty.is_char() {217 return Some(make::ext::empty_char());218 }219 if builtin_ty.is_str() {220 return Some(make::ext::empty_str());221 }222 if builtin_ty.is_bool() {223 return Some(make::ext::default_bool());224 }225 }226227 let krate = ctx228 .sema229 .file_to_module_def(d.file.original_file(ctx.sema.db).file_id(ctx.sema.db))?230 .krate(ctx.sema.db);231 let module = krate.root_module(ctx.sema.db);232233 // Look for a ::new() associated function234 let has_new_func = ty235 .iterate_assoc_items(ctx.sema.db, |assoc_item| {236 if let AssocItem::Function(func) = assoc_item237 && func.name(ctx.sema.db) == sym::new238 && func.assoc_fn_params(ctx.sema.db).is_empty()239 {240 return Some(());241 }242243 None244 })245 .is_some();246247 let famous_defs = FamousDefs(&ctx.sema, krate);248 if has_new_func {249 Some(make::ext::expr_ty_new(&make_ty(ty, ctx.sema.db, module, ctx.edition)))250 } else if ty.as_adt() == famous_defs.core_option_Option()?.ty(ctx.sema.db).as_adt() {251 Some(make::ext::option_none())252 } else if !ty.is_array()253 && ty.impls_trait(ctx.sema.db, famous_defs.core_default_Default()?, &[])254 {255 Some(make::ext::expr_ty_default(&make_ty(ty, ctx.sema.db, module, ctx.edition)))256 } else {257 None258 }259}260261#[cfg(test)]262mod tests {263 use crate::tests::{check_diagnostics, check_fix, check_no_fix};264265 #[test]266 fn missing_record_pat_field_diagnostic() {267 check_diagnostics(268 r#"269struct S { foo: i32, bar: () }270fn baz(s: S) {271 let S { foo: _ } = s;272 //^ 💡 error: missing structure fields:273 //| - bar274}275"#,276 );277 }278279 #[test]280 fn missing_record_pat_field_no_diagnostic_if_not_exhaustive() {281 check_diagnostics(282 r"283struct S { foo: i32, bar: () }284fn baz(s: S) -> i32 {285 match s {286 S { foo, .. } => foo,287 }288}289",290 )291 }292293 #[test]294 fn missing_record_pat_field_box() {295 check_diagnostics(296 r"297struct S { s: Box<u32> }298fn x(a: S) {299 let S { box s } = a;300}301",302 )303 }304305 #[test]306 fn missing_record_pat_field_ref() {307 check_diagnostics(308 r"309struct S { s: u32 }310fn x(a: S) {311 let S { ref s } = a;312 _ = s;313}314",315 )316 }317318 #[test]319 fn missing_record_expr_in_assignee_expr() {320 check_diagnostics(321 r"322struct S { s: usize, t: usize }323struct S2 { s: S, t: () }324struct T(S);325fn regular(a: S) {326 let s;327 S { s, .. } = a;328 _ = s;329}330fn nested(a: S2) {331 let s;332 S2 { s: S { s, .. }, .. } = a;333 _ = s;334}335fn in_tuple(a: (S,)) {336 let s;337 (S { s, .. },) = a;338 _ = s;339}340fn in_array(a: [S;1]) {341 let s;342 [S { s, .. },] = a;343 _ = s;344}345fn in_tuple_struct(a: T) {346 let s;347 T(S { s, .. }) = a;348 _ = s;349}350 ",351 );352 }353354 #[test]355 fn range_mapping_out_of_macros() {356 check_fix(357 r#"358fn some() {}359fn items() {}360fn here() {}361362macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }363364fn main() {365 let _x = id![Foo { a: $042 }];366}367368pub struct Foo { pub a: i32, pub b: i32 }369"#,370 r#"371fn some() {}372fn items() {}373fn here() {}374375macro_rules! id { ($($tt:tt)*) => { $($tt)*}; }376377fn main() {378 let _x = id![Foo {a:42, b: 0 }];379}380381pub struct Foo { pub a: i32, pub b: i32 }382"#,383 );384 }385386 #[test]387 fn test_fill_struct_fields_empty() {388 check_fix(389 r#"390//- minicore: option391struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }392393fn test_fn() {394 let s = TestStruct {$0};395}396"#,397 r#"398struct TestStruct { one: i32, two: i64, three: Option<i32>, four: bool }399400fn test_fn() {401 let s = TestStruct { one: 0, two: 0, three: None, four: false };402}403"#,404 );405 }406407 #[test]408 fn test_fill_struct_zst_fields() {409 check_fix(410 r#"411struct Empty;412413struct TestStruct { one: i32, two: Empty }414415fn test_fn() {416 let s = TestStruct {$0};417}418"#,419 r#"420struct Empty;421422struct TestStruct { one: i32, two: Empty }423424fn test_fn() {425 let s = TestStruct { one: 0, two: Empty };426}427"#,428 );429 check_fix(430 r#"431enum Empty { Foo };432433struct TestStruct { one: i32, two: Empty }434435fn test_fn() {436 let s = TestStruct {$0};437}438"#,439 r#"440enum Empty { Foo };441442struct TestStruct { one: i32, two: Empty }443444fn test_fn() {445 let s = TestStruct { one: 0, two: Empty::Foo };446}447"#,448 );449450 // make sure the assist doesn't fill non Unit variants451 check_fix(452 r#"453struct Empty {};454455struct TestStruct { one: i32, two: Empty }456457fn test_fn() {458 let s = TestStruct {$0};459}460"#,461 r#"462struct Empty {};463464struct TestStruct { one: i32, two: Empty }465466fn test_fn() {467 let s = TestStruct { one: 0, two: todo!() };468}469"#,470 );471 check_fix(472 r#"473enum Empty { Foo {} };474475struct TestStruct { one: i32, two: Empty }476477fn test_fn() {478 let s = TestStruct {$0};479}480"#,481 r#"482enum Empty { Foo {} };483484struct TestStruct { one: i32, two: Empty }485486fn test_fn() {487 let s = TestStruct { one: 0, two: todo!() };488}489"#,490 );491 }492493 #[test]494 fn test_fill_struct_fields_self() {495 check_fix(496 r#"497struct TestStruct { one: i32 }498499impl TestStruct {500 fn test_fn() { let s = Self {$0}; }501}502"#,503 r#"504struct TestStruct { one: i32 }505506impl TestStruct {507 fn test_fn() { let s = Self { one: 0 }; }508}509"#,510 );511 }512513 #[test]514 fn test_fill_struct_fields_enum() {515 check_fix(516 r#"517enum Expr {518 Bin { lhs: Box<Expr>, rhs: Box<Expr> }519}520521impl Expr {522 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {523 Expr::Bin {$0 }524 }525}526"#,527 r#"528enum Expr {529 Bin { lhs: Box<Expr>, rhs: Box<Expr> }530}531532impl Expr {533 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {534 Expr::Bin { lhs, rhs }535 }536}537"#,538 );539 }540541 #[test]542 fn test_fill_struct_fields_partial() {543 check_fix(544 r#"545struct TestStruct { one: i32, two: i64 }546547fn test_fn() {548 let s = TestStruct{ two: 2$0 };549}550"#,551 r"552struct TestStruct { one: i32, two: i64 }553554fn test_fn() {555 let s = TestStruct{ two: 2, one: 0 };556}557",558 );559 }560561 #[test]562 fn test_fill_struct_fields_new() {563 check_fix(564 r#"565struct TestWithNew(usize);566impl TestWithNew {567 pub fn new() -> Self {568 Self(0)569 }570}571struct TestStruct { one: i32, two: TestWithNew }572573fn test_fn() {574 let s = TestStruct{ $0 };575}576"#,577 r"578struct TestWithNew(usize);579impl TestWithNew {580 pub fn new() -> Self {581 Self(0)582 }583}584struct TestStruct { one: i32, two: TestWithNew }585586fn test_fn() {587 let s = TestStruct{ one: 0, two: TestWithNew::new() };588}589",590 );591 }592593 #[test]594 fn test_fill_struct_fields_default() {595 check_fix(596 r#"597//- minicore: default, option, slice598struct TestWithDefault(usize);599impl Default for TestWithDefault {600 pub fn default() -> Self {601 Self(0)602 }603}604struct TestStruct { one: i32, two: TestWithDefault, r: &'static [i32] }605606fn test_fn() {607 let s = TestStruct{ $0 };608}609"#,610 r"611struct TestWithDefault(usize);612impl Default for TestWithDefault {613 pub fn default() -> Self {614 Self(0)615 }616}617struct TestStruct { one: i32, two: TestWithDefault, r: &'static [i32] }618619fn test_fn() {620 let s = TestStruct{ one: 0, two: TestWithDefault::default(), r: <&'static [i32]>::default() };621}622",623 );624 }625626 #[test]627 fn test_fill_struct_fields_raw_ident() {628 check_fix(629 r#"630struct TestStruct { r#type: u8 }631632fn test_fn() {633 TestStruct { $0 };634}635"#,636 r"637struct TestStruct { r#type: u8 }638639fn test_fn() {640 TestStruct { r#type: 0 };641}642",643 );644 }645646 #[test]647 fn test_fill_struct_fields_no_diagnostic() {648 check_diagnostics(649 r#"650struct TestStruct { one: i32, two: i64 }651652fn test_fn() {653 let one = 1;654 let _s = TestStruct{ one, two: 2 };655}656 "#,657 );658 }659660 #[test]661 fn test_fill_struct_fields_no_diagnostic_on_spread() {662 check_diagnostics(663 r#"664struct TestStruct { one: i32, two: i64 }665666fn test_fn() {667 let one = 1;668 let a = TestStruct{ one, two: 2 };669 let _ = TestStruct{ ..a };670}671"#,672 );673 }674675 #[test]676 fn test_fill_struct_fields_blank_line() {677 check_fix(678 r#"679struct S { a: (), b: () }680681fn f() {682 S {683 $0684 };685}686"#,687 r#"688struct S { a: (), b: () }689690fn f() {691 S {692 a: todo!(),693 b: todo!(),694 };695}696"#,697 );698 }699700 #[test]701 fn test_fill_struct_fields_shorthand() {702 cov_mark::check!(field_shorthand);703 check_fix(704 r#"705struct S { a: &'static str, b: i32 }706707fn f() {708 let a = "hello";709 let b = 1i32;710 S {711 $0712 };713}714"#,715 r#"716struct S { a: &'static str, b: i32 }717718fn f() {719 let a = "hello";720 let b = 1i32;721 S {722 a,723 b,724 };725}726"#,727 );728 }729730 #[test]731 fn test_fill_struct_fields_shorthand_ty_mismatch() {732 check_fix(733 r#"734struct S { a: &'static str, b: i32 }735736fn f() {737 let a = "hello";738 let b = 1usize;739 S {740 $0741 };742}743"#,744 r#"745struct S { a: &'static str, b: i32 }746747fn f() {748 let a = "hello";749 let b = 1usize;750 S {751 a,752 b: 0,753 };754}755"#,756 );757 }758759 #[test]760 fn test_fill_struct_fields_shorthand_unifies() {761 check_fix(762 r#"763struct S<T> { a: &'static str, b: T }764765fn f() {766 let a = "hello";767 let b = 1i32;768 S {769 $0770 };771}772"#,773 r#"774struct S<T> { a: &'static str, b: T }775776fn f() {777 let a = "hello";778 let b = 1i32;779 S {780 a,781 b,782 };783}784"#,785 );786 }787788 #[test]789 fn test_fill_struct_pat_fields() {790 check_fix(791 r#"792struct S { a: &'static str, b: i32 }793794fn f() {795 let S {796 $0797 };798}799"#,800 r#"801struct S { a: &'static str, b: i32 }802803fn f() {804 let S {805 a,806 b,807 };808}809"#,810 );811 }812813 #[test]814 fn test_fill_struct_pat_fields_partial() {815 check_fix(816 r#"817struct S { a: &'static str, b: i32 }818819fn f() {820 let S {821 a,$0822 };823}824"#,825 r#"826struct S { a: &'static str, b: i32 }827828fn f() {829 let S {830 a,831 b,832 };833}834"#,835 );836 }837838 #[test]839 fn import_extern_crate_clash_with_inner_item() {840 // This is more of a resolver test, but doesn't really work with the hir_def testsuite.841842 check_diagnostics(843 r#"844//- /lib.rs crate:lib deps:jwt845mod permissions;846847use permissions::jwt;848849fn f() {850 fn inner() {}851 jwt::Claims {}; // should resolve to the local one with 0 fields, and not get a diagnostic852}853854//- /permissions.rs855pub mod jwt {856 pub struct Claims {}857}858859//- /jwt/lib.rs crate:jwt860pub struct Claims {861 field: u8,862}863 "#,864 );865 }866867 #[test]868 fn test_default_field_values_basic() {869 // This should work without errors - only field 'b' is required870 check_diagnostics(871 r#"872#![feature(default_field_values)]873struct Struct {874 a: usize = 0,875 b: usize,876}877878fn main() {879 Struct { b: 1, .. };880}881"#,882 );883 }884885 #[test]886 fn test_default_field_values_missing_field_error() {887 // This should report a missing field error because email is required888 check_diagnostics(889 r#"890#![feature(default_field_values)]891struct UserInfo {892 id: i32,893 age: f32 = 1.0,894 email: String,895}896897fn main() {898 UserInfo { id: 20, .. };899// ^^^^^^^^💡 error: missing structure fields:900// |- email901}902"#,903 );904 }905906 #[test]907 fn test_default_field_values_requires_spread_syntax() {908 // without `..` should report missing fields909 check_diagnostics(910 r#"911#![feature(default_field_values)]912struct Point {913 x: i32 = 0,914 y: i32 = 0,915}916917fn main() {918 Point { x: 0 };919// ^^^^^💡 error: missing structure fields:920// |- y921}922"#,923 );924 }925926 #[test]927 fn test_default_field_values_pattern_matching() {928 check_diagnostics(929 r#"930#![feature(default_field_values)]931struct Point {932 x: i32 = 0,933 y: i32 = 0,934 z: i32,935}936937fn main() {938 let Point { x, .. } = Point { z: 5, .. };939}940"#,941 );942 }943944 #[test]945 fn coerce_existing_local() {946 check_fix(947 r#"948struct A {949 v: f64,950}951952fn f() -> A {953 let v = loop {};954 A {$0}955}956 "#,957 r#"958struct A {959 v: f64,960}961962fn f() -> A {963 let v = loop {};964 A { v }965}966 "#,967 );968 }969970 #[test]971 fn inaccessible_fields() {972 check_no_fix(973 r#"974mod foo {975 pub struct Bar { baz: i32 }976}977978fn qux() {979 foo::Bar {$0};980}981 "#,982 );983 }984}