about summary refs log tree commit diff
path: root/compiler/rustc_resolve/src
diff options
context:
space:
mode:
authorEsteban Küber <esteban@kuber.com.ar>2024-12-13 21:51:33 +0000
committerEsteban Küber <esteban@kuber.com.ar>2024-12-13 21:51:33 +0000
commit0f82cfffda97be546949f974586d007b51e2b36e (patch)
tree27e78cf27fae3181e422197ba0f1770be4a0d0aa /compiler/rustc_resolve/src
parent21fe748be15271ea5804e0507cd699b675efe038 (diff)
downloadrust-0f82cfffda97be546949f974586d007b51e2b36e.tar.gz
rust-0f82cfffda97be546949f974586d007b51e2b36e.zip
Keep track of patterns that could have introduced a binding, but didn't
When we recover from a pattern parse error, or a pattern uses `..`, we keep track of that and affect resolution error for missing bindings that could have been provided by that pattern. We differentiate between `..` and parse recovery. We silence resolution errors likely caused by the pattern parse error.

```
error[E0425]: cannot find value `title` in this scope
  --> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:19:30
   |
LL |         println!("[{}]({})", title, url);
   |                              ^^^^^ not found in this scope
   |
note: `Website` has a field `title` which could have been included in this pattern, but it wasn't
  --> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:17:12
   |
LL | / struct Website {
LL | |     url: String,
LL | |     title: Option<String> ,
   | |     ----- defined here
LL | | }
   | |_-
...
LL |       if let Website { url, .. } = website {
   |              ^^^^^^^^^^^^^^^^^^^ this pattern doesn't include `title`, which is available in `Website`
```

Fix #74863.
Diffstat (limited to 'compiler/rustc_resolve/src')
-rw-r--r--compiler/rustc_resolve/src/late.rs33
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs60
2 files changed, 90 insertions, 3 deletions
diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs
index 789d74876f7..830b175b26b 100644
--- a/compiler/rustc_resolve/src/late.rs
+++ b/compiler/rustc_resolve/src/late.rs
@@ -264,12 +264,17 @@ impl RibKind<'_> {
 #[derive(Debug)]
 pub(crate) struct Rib<'ra, R = Res> {
     pub bindings: IdentMap<R>,
+    pub patterns_with_skipped_bindings: FxHashMap<DefId, Vec<(Span, bool /* recovered error */)>>,
     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 +3758,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 +3767,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 +3792,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 +3827,27 @@ 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, matches!(rest, ast::PatFieldsRest::Recovered(_))));
+                }
+            }
+            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 09f3e848766..660ac2d37cc 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,65 @@ 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 };
+        'outer: 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(|(_, was_recovered)| *was_recovered) {
+                                // 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::<Vec<_>>().into();
+                                err.span_note(
+                                    multispan,
+                                    "this pattern had a recovered parse error which likely \
+                                        lost the expected fields",
+                                );
+                                err.downgrade_to_delayed_bug();
+                            }
+                            let mut multispan: MultiSpan = spans
+                                .iter()
+                                .filter(|(_, was_recovered)| !was_recovered)
+                                .map(|(sp, _)| *sp)
+                                .collect::<Vec<_>>()
+                                .into();
+                            let def_span = self.r.def_span(*def_id);
+                            let ty = self.r.tcx.item_name(*def_id);
+                            multispan.push_span_label(def_span, String::new());
+                            multispan.push_span_label(field.span, "defined here".to_string());
+                            for (span, _) in spans.iter().filter(|(_, r)| !r) {
+                                multispan.push_span_label(
+                                    *span,
+                                    format!(
+                                        "this pattern doesn't include `{field}`, which is \
+                                         available in `{ty}`",
+                                    ),
+                                );
+                            }
+                            err.span_note(
+                                multispan,
+                                format!(
+                                    "`{ty}` has a field `{field}` which could have been included \
+                                     in this pattern, but it wasn't",
+                                ),
+                            );
+                            break 'outer;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     fn suggest_at_operator_in_slice_pat_with_range(
         &mut self,
         err: &mut Diag<'_>,