diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 650525a2f520e..57f1ac6335398 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -859,6 +859,8 @@ pub enum PatKind { pub enum PatFieldsRest { /// `module::StructName { field, ..}` Rest, + /// `module::StructName { field, syntax error }` + Recovered(ErrorGuaranteed), /// `module::StructName { field }` None, } diff --git a/compiler/rustc_ast_lowering/src/pat.rs b/compiler/rustc_ast_lowering/src/pat.rs index c4bae084a3f8c..6066054859058 100644 --- a/compiler/rustc_ast_lowering/src/pat.rs +++ b/compiler/rustc_ast_lowering/src/pat.rs @@ -92,7 +92,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> { span: self.lower_span(f.span), } })); - break hir::PatKind::Struct(qpath, fs, *etc == ast::PatFieldsRest::Rest); + break hir::PatKind::Struct( + qpath, + fs, + matches!( + etc, + ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) + ), + ); } PatKind::Tuple(pats) => { let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple"); diff --git a/compiler/rustc_ast_pretty/src/pprust/state.rs b/compiler/rustc_ast_pretty/src/pprust/state.rs index 49e4a559e7382..c6f106c3388bc 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state.rs @@ -1653,11 +1653,14 @@ impl<'a> State<'a> { }, |f| f.pat.span, ); - if *etc == ast::PatFieldsRest::Rest { + if let ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) = etc { if !fields.is_empty() { self.word_space(","); } self.word(".."); + if let ast::PatFieldsRest::Recovered(_) = etc { + self.word("/* recovered parse error */"); + } } if !empty { self.space(); diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs index 4cda887a02bdb..255be721525c6 100644 --- a/compiler/rustc_parse/src/parser/pat.rs +++ b/compiler/rustc_parse/src/parser/pat.rs @@ -1371,10 +1371,10 @@ impl<'a> Parser<'a> { self.bump(); let (fields, etc) = self.parse_pat_fields().unwrap_or_else(|mut e| { e.span_label(path.span, "while parsing the fields for this pattern"); - e.emit(); + let guar = e.emit(); self.recover_stmt(); // When recovering, pretend we had `Foo { .. }`, to avoid cascading errors. - (ThinVec::new(), PatFieldsRest::Rest) + (ThinVec::new(), PatFieldsRest::Recovered(guar)) }); self.bump(); Ok(PatKind::Struct(qself, path, fields, etc)) diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 789d74876f722..4e57154ff2b55 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -19,7 +19,9 @@ use rustc_ast::visit::{ use rustc_ast::*; use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap}; use rustc_errors::codes::*; -use rustc_errors::{Applicability, DiagArgValue, IntoDiagArg, StashKey, Suggestions}; +use rustc_errors::{ + Applicability, DiagArgValue, ErrorGuaranteed, IntoDiagArg, StashKey, Suggestions, +}; use rustc_hir::def::Namespace::{self, *}; use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS}; use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId}; @@ -264,12 +266,17 @@ impl RibKind<'_> { #[derive(Debug)] pub(crate) struct Rib<'ra, R = Res> { pub bindings: IdentMap, + pub patterns_with_skipped_bindings: FxHashMap)>>, pub kind: RibKind<'ra>, } impl<'ra, R> Rib<'ra, R> { fn new(kind: RibKind<'ra>) -> Rib<'ra, R> { - Rib { bindings: Default::default(), kind } + Rib { + bindings: Default::default(), + patterns_with_skipped_bindings: Default::default(), + kind, + } } } @@ -3753,6 +3760,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { /// When a whole or-pattern has been dealt with, the thing happens. /// /// See the implementation and `fresh_binding` for more details. + #[tracing::instrument(skip(self, bindings), level = "debug")] fn resolve_pattern_inner( &mut self, pat: &Pat, @@ -3761,7 +3769,6 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ) { // Visit all direct subpatterns of this pattern. pat.walk(&mut |pat| { - debug!("resolve_pattern pat={:?} node={:?}", pat, pat.kind); match pat.kind { PatKind::Ident(bmode, ident, ref sub) => { // First try to resolve the identifier as some existing entity, @@ -3787,8 +3794,9 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { PatKind::Path(ref qself, ref path) => { self.smart_resolve_path(pat.id, qself, path, PathSource::Pat); } - PatKind::Struct(ref qself, ref path, ..) => { + PatKind::Struct(ref qself, ref path, ref _fields, ref rest) => { self.smart_resolve_path(pat.id, qself, path, PathSource::Struct); + self.record_patterns_with_skipped_bindings(pat, rest); } PatKind::Or(ref ps) => { // Add a new set of bindings to the stack. `Or` here records that when a @@ -3821,6 +3829,30 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { }); } + fn record_patterns_with_skipped_bindings(&mut self, pat: &Pat, rest: &ast::PatFieldsRest) { + match rest { + ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) => { + // Record that the pattern doesn't introduce all the bindings it could. + if let Some(partial_res) = self.r.partial_res_map.get(&pat.id) + && let Some(res) = partial_res.full_res() + && let Some(def_id) = res.opt_def_id() + { + self.ribs[ValueNS] + .last_mut() + .unwrap() + .patterns_with_skipped_bindings + .entry(def_id) + .or_default() + .push((pat.span, match rest { + ast::PatFieldsRest::Recovered(guar) => Err(*guar), + _ => Ok(()), + })); + } + } + ast::PatFieldsRest::None => {} + } + } + fn fresh_binding( &mut self, ident: Ident, diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 09f3e84876689..85ea6a74d3cfc 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -430,6 +430,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { let mut err = self.r.dcx().struct_span_err(base_error.span, base_error.msg.clone()); err.code(code); + self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg); self.suggest_at_operator_in_slice_pat_with_range(&mut err, path); self.suggest_swapping_misplaced_self_ty_and_trait(&mut err, source, res, base_error.span); @@ -1120,6 +1121,48 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { true } + fn detect_missing_binding_available_from_pattern( + &mut self, + err: &mut Diag<'_>, + path: &[Segment], + following_seg: Option<&Segment>, + ) { + let [segment] = path else { return }; + let None = following_seg else { return }; + for rib in self.ribs[ValueNS].iter().rev() { + for (def_id, spans) in &rib.patterns_with_skipped_bindings { + if let Some(fields) = self.r.field_idents(*def_id) { + for field in fields { + if field.name == segment.ident.name { + if spans.iter().all(|(_, had_error)| had_error.is_err()) { + // This resolution error will likely be fixed by fixing a + // syntax error in a pattern, so it is irrelevant to the user. + let multispan: MultiSpan = + spans.iter().map(|(s, _)| *s).collect::>().into(); + err.span_note( + multispan, + "this pattern had a recovered parse error which likely lost \ + the expected fields", + ); + err.downgrade_to_delayed_bug(); + } + let ty = self.r.tcx.item_name(*def_id); + for (span, _) in spans { + err.span_label( + *span, + format!( + "this pattern doesn't include `{field}`, which is \ + available in `{ty}`", + ), + ); + } + } + } + } + } + } + } + fn suggest_at_operator_in_slice_pat_with_range( &mut self, err: &mut Diag<'_>, diff --git a/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.rs b/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.rs new file mode 100644 index 0000000000000..225891e390ff5 --- /dev/null +++ b/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.rs @@ -0,0 +1,21 @@ +struct Website { + url: String, + title: Option, +} + +fn main() { + let website = Website { + url: "http://www.example.com".into(), + title: Some("Example Domain".into()), + }; + + if let Website { url, Some(title) } = website { //~ ERROR expected `,` + //~^ NOTE while parsing the fields for this pattern + println!("[{}]({})", title, url); // we hide the errors for `title` and `url` + } + + if let Website { url, .. } = website { //~ NOTE this pattern + println!("[{}]({})", title, url); //~ ERROR cannot find value `title` in this scope + //~^ NOTE not found in this scope + } +} diff --git a/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.stderr b/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.stderr new file mode 100644 index 0000000000000..80fcd714400d6 --- /dev/null +++ b/tests/ui/pattern/struct-pattern-with-missing-fields-resolve-error.stderr @@ -0,0 +1,19 @@ +error: expected `,` + --> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:12:31 + | +LL | if let Website { url, Some(title) } = website { + | ------- ^ + | | + | while parsing the fields for this pattern + +error[E0425]: cannot find value `title` in this scope + --> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:18:30 + | +LL | if let Website { url, .. } = website { + | ------------------- this pattern doesn't include `title`, which is available in `Website` +LL | println!("[{}]({})", title, url); + | ^^^^^ not found in this scope + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0425`.