Rollup merge of #134284 - estebank:issue-74863, r=lcnr

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: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
```

Fix #74863.
This commit is contained in:
Matthias Krüger 2024-12-16 20:00:22 +01:00 committed by GitHub
commit 86db97e2b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 135 additions and 8 deletions

View file

@ -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<R>,
pub patterns_with_skipped_bindings: FxHashMap<DefId, Vec<(Span, Result<(), ErrorGuaranteed>)>>,
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,
}
}
}
@ -3775,6 +3782,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,
@ -3783,7 +3791,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,
@ -3809,8 +3816,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
@ -3843,6 +3851,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,

View file

@ -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::<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 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<'_>,