From: Yuki Okushi Date: Sun, 19 Sep 2021 08:31:29 +0000 (+0900) Subject: Rollup merge of #87960 - hkmatsumoto:suggest-inexisting-field-for-unmentioned-field... X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=ebd31f5f1a51099038935e679b0eb92afa3364a5;hp=-c;p=rust.git Rollup merge of #87960 - hkmatsumoto:suggest-inexisting-field-for-unmentioned-field, r=estebank Suggest replacing an inexisting field for an unmentioned field Fix #87938 This PR adds a suggestion to replace an inexisting field for an unmentioned field. Given the following code: ```rust enum Foo { Bar { alpha: u8, bravo: u8, charlie: u8 }, } fn foo(foo: Foo) { match foo { Foo::Bar { alpha, beta, // `bravo` miswritten as `beta` here. charlie, } => todo!(), } } ``` the compiler now emits the error messages below. ```text error[E0026]: variant `Foo::Bar` does not have a field named `beta` --> src/lib.rs:9:13 | 9 | beta, // `bravo` miswritten as `beta` here. | ^^^^ | | | variant `Foo::Bar` does not have this field | help: `Foo::Bar` has a field named `bravo`: `bravo` ``` Note that this suggestion is available iff the number of inexisting fields and unmentioned fields are both 1. --- ebd31f5f1a51099038935e679b0eb92afa3364a5 diff --combined compiler/rustc_typeck/src/check/pat.rs index a056ab6aef2,800f413e9d8..829268e3cb5 --- a/compiler/rustc_typeck/src/check/pat.rs +++ b/compiler/rustc_typeck/src/check/pat.rs @@@ -11,7 -11,6 +11,7 @@@ use rustc_infer::infer use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; use rustc_middle::ty::subst::GenericArg; use rustc_middle::ty::{self, Adt, BindingMode, Ty, TypeFoldable}; +use rustc_session::lint::builtin::NON_EXHAUSTIVE_OMITTED_PATTERNS; use rustc_span::hygiene::DesugaringKind; use rustc_span::lev_distance::find_best_match_for_name; use rustc_span::source_map::{Span, Spanned}; @@@ -1262,8 -1261,7 +1262,8 @@@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> }; // Require `..` if struct has non_exhaustive attribute. - if variant.is_field_list_non_exhaustive() && !adt.did.is_local() && !etc { + let non_exhaustive = variant.is_field_list_non_exhaustive() && !adt.did.is_local(); + if non_exhaustive && !etc { self.error_foreign_non_exhaustive_spat(pat, adt.variant_descr(), fields.is_empty()); } @@@ -1278,7 -1276,7 +1278,7 @@@ if etc { tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit(); } - } else if !etc && !unmentioned_fields.is_empty() { + } else if !unmentioned_fields.is_empty() { let accessible_unmentioned_fields: Vec<_> = unmentioned_fields .iter() .copied() @@@ -1286,19 -1284,16 +1286,19 @@@ field.vis.is_accessible_from(tcx.parent_module(pat.hir_id).to_def_id(), tcx) }) .collect(); - - if accessible_unmentioned_fields.is_empty() { - unmentioned_err = Some(self.error_no_accessible_fields(pat, &fields)); - } else { - unmentioned_err = Some(self.error_unmentioned_fields( - pat, - &accessible_unmentioned_fields, - accessible_unmentioned_fields.len() != unmentioned_fields.len(), - &fields, - )); + if non_exhaustive { + self.non_exhaustive_reachable_pattern(pat, &accessible_unmentioned_fields, adt_ty) + } else if !etc { + if accessible_unmentioned_fields.is_empty() { + unmentioned_err = Some(self.error_no_accessible_fields(pat, &fields)); + } else { + unmentioned_err = Some(self.error_unmentioned_fields( + pat, + &accessible_unmentioned_fields, + accessible_unmentioned_fields.len() != unmentioned_fields.len(), + &fields, + )); + } } } match (inexistent_fields_err, unmentioned_err) { @@@ -1452,7 -1447,8 +1452,8 @@@ plural ), ); - if plural == "" { + + if unmentioned_fields.len() == 1 { let input = unmentioned_fields.iter().map(|(_, field)| field.name).collect::>(); let suggested_name = find_best_match_for_name(&input, ident.name, None); @@@ -1473,6 -1469,18 +1474,18 @@@ // We don't want to throw `E0027` in case we have thrown `E0026` for them. unmentioned_fields.retain(|&(_, x)| x.name != suggested_name); } + } else if inexistent_fields.len() == 1 { + let unmentioned_field = unmentioned_fields[0].1.name; + err.span_suggestion_short( + ident.span, + &format!( + "`{}` has a field named `{}`", + tcx.def_path_str(variant.def_id), + unmentioned_field + ), + unmentioned_field.to_string(), + Applicability::MaybeIncorrect, + ); } } } @@@ -1609,51 -1617,6 +1622,51 @@@ err } + /// Report that a pattern for a `#[non_exhaustive]` struct marked with `non_exhaustive_omitted_patterns` + /// is not exhaustive enough. + /// + /// Nb: the partner lint for enums lives in `compiler/rustc_mir_build/src/thir/pattern/usefulness.rs`. + fn non_exhaustive_reachable_pattern( + &self, + pat: &Pat<'_>, + unmentioned_fields: &[(&ty::FieldDef, Ident)], + ty: Ty<'tcx>, + ) { + fn joined_uncovered_patterns(witnesses: &[&Ident]) -> String { + const LIMIT: usize = 3; + match witnesses { + [] => bug!(), + [witness] => format!("`{}`", witness), + [head @ .., tail] if head.len() < LIMIT => { + let head: Vec<_> = head.iter().map(<_>::to_string).collect(); + format!("`{}` and `{}`", head.join("`, `"), tail) + } + _ => { + let (head, tail) = witnesses.split_at(LIMIT); + let head: Vec<_> = head.iter().map(<_>::to_string).collect(); + format!("`{}` and {} more", head.join("`, `"), tail.len()) + } + } + } + let joined_patterns = joined_uncovered_patterns( + &unmentioned_fields.iter().map(|(_, i)| i).collect::>(), + ); + + self.tcx.struct_span_lint_hir(NON_EXHAUSTIVE_OMITTED_PATTERNS, pat.hir_id, pat.span, |build| { + let mut lint = build.build("some fields are not explicitly listed"); + lint.span_label(pat.span, format!("field{} {} not listed", rustc_errors::pluralize!(unmentioned_fields.len()), joined_patterns)); + + lint.help( + "ensure that all fields are mentioned explicitly by adding the suggested fields", + ); + lint.note(&format!( + "the pattern is of type `{}` and the `non_exhaustive_omitted_patterns` attribute was found", + ty, + )); + lint.emit(); + }); + } + /// Returns a diagnostic reporting a struct pattern which does not mention some fields. /// /// ```text