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:
commit
86db97e2b3
8 changed files with 135 additions and 8 deletions
|
@ -859,6 +859,8 @@ pub enum PatKind {
|
||||||
pub enum PatFieldsRest {
|
pub enum PatFieldsRest {
|
||||||
/// `module::StructName { field, ..}`
|
/// `module::StructName { field, ..}`
|
||||||
Rest,
|
Rest,
|
||||||
|
/// `module::StructName { field, syntax error }`
|
||||||
|
Recovered(ErrorGuaranteed),
|
||||||
/// `module::StructName { field }`
|
/// `module::StructName { field }`
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
span: self.lower_span(f.span),
|
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) => {
|
PatKind::Tuple(pats) => {
|
||||||
let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple");
|
let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple");
|
||||||
|
|
|
@ -1654,11 +1654,14 @@ impl<'a> State<'a> {
|
||||||
},
|
},
|
||||||
|f| f.pat.span,
|
|f| f.pat.span,
|
||||||
);
|
);
|
||||||
if *etc == ast::PatFieldsRest::Rest {
|
if let ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) = etc {
|
||||||
if !fields.is_empty() {
|
if !fields.is_empty() {
|
||||||
self.word_space(",");
|
self.word_space(",");
|
||||||
}
|
}
|
||||||
self.word("..");
|
self.word("..");
|
||||||
|
if let ast::PatFieldsRest::Recovered(_) = etc {
|
||||||
|
self.word("/* recovered parse error */");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !empty {
|
if !empty {
|
||||||
self.space();
|
self.space();
|
||||||
|
|
|
@ -1371,10 +1371,10 @@ impl<'a> Parser<'a> {
|
||||||
self.bump();
|
self.bump();
|
||||||
let (fields, etc) = self.parse_pat_fields().unwrap_or_else(|mut e| {
|
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.span_label(path.span, "while parsing the fields for this pattern");
|
||||||
e.emit();
|
let guar = e.emit();
|
||||||
self.recover_stmt();
|
self.recover_stmt();
|
||||||
// When recovering, pretend we had `Foo { .. }`, to avoid cascading errors.
|
// When recovering, pretend we had `Foo { .. }`, to avoid cascading errors.
|
||||||
(ThinVec::new(), PatFieldsRest::Rest)
|
(ThinVec::new(), PatFieldsRest::Recovered(guar))
|
||||||
});
|
});
|
||||||
self.bump();
|
self.bump();
|
||||||
Ok(PatKind::Struct(qself, path, fields, etc))
|
Ok(PatKind::Struct(qself, path, fields, etc))
|
||||||
|
|
|
@ -19,7 +19,9 @@ use rustc_ast::visit::{
|
||||||
use rustc_ast::*;
|
use rustc_ast::*;
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
|
||||||
use rustc_errors::codes::*;
|
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::Namespace::{self, *};
|
||||||
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
|
use rustc_hir::def::{self, CtorKind, DefKind, LifetimeRes, NonMacroAttrKind, PartialRes, PerNS};
|
||||||
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
|
use rustc_hir::def_id::{CRATE_DEF_ID, DefId, LOCAL_CRATE, LocalDefId};
|
||||||
|
@ -264,12 +266,17 @@ impl RibKind<'_> {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Rib<'ra, R = Res> {
|
pub(crate) struct Rib<'ra, R = Res> {
|
||||||
pub bindings: IdentMap<R>,
|
pub bindings: IdentMap<R>,
|
||||||
|
pub patterns_with_skipped_bindings: FxHashMap<DefId, Vec<(Span, Result<(), ErrorGuaranteed>)>>,
|
||||||
pub kind: RibKind<'ra>,
|
pub kind: RibKind<'ra>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'ra, R> Rib<'ra, R> {
|
impl<'ra, R> Rib<'ra, R> {
|
||||||
fn new(kind: RibKind<'ra>) -> 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.
|
/// When a whole or-pattern has been dealt with, the thing happens.
|
||||||
///
|
///
|
||||||
/// See the implementation and `fresh_binding` for more details.
|
/// See the implementation and `fresh_binding` for more details.
|
||||||
|
#[tracing::instrument(skip(self, bindings), level = "debug")]
|
||||||
fn resolve_pattern_inner(
|
fn resolve_pattern_inner(
|
||||||
&mut self,
|
&mut self,
|
||||||
pat: &Pat,
|
pat: &Pat,
|
||||||
|
@ -3783,7 +3791,6 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
|
||||||
) {
|
) {
|
||||||
// Visit all direct subpatterns of this pattern.
|
// Visit all direct subpatterns of this pattern.
|
||||||
pat.walk(&mut |pat| {
|
pat.walk(&mut |pat| {
|
||||||
debug!("resolve_pattern pat={:?} node={:?}", pat, pat.kind);
|
|
||||||
match pat.kind {
|
match pat.kind {
|
||||||
PatKind::Ident(bmode, ident, ref sub) => {
|
PatKind::Ident(bmode, ident, ref sub) => {
|
||||||
// First try to resolve the identifier as some existing entity,
|
// 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) => {
|
PatKind::Path(ref qself, ref path) => {
|
||||||
self.smart_resolve_path(pat.id, qself, path, PathSource::Pat);
|
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.smart_resolve_path(pat.id, qself, path, PathSource::Struct);
|
||||||
|
self.record_patterns_with_skipped_bindings(pat, rest);
|
||||||
}
|
}
|
||||||
PatKind::Or(ref ps) => {
|
PatKind::Or(ref ps) => {
|
||||||
// Add a new set of bindings to the stack. `Or` here records that when a
|
// 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(
|
fn fresh_binding(
|
||||||
&mut self,
|
&mut self,
|
||||||
ident: Ident,
|
ident: Ident,
|
||||||
|
|
|
@ -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());
|
let mut err = self.r.dcx().struct_span_err(base_error.span, base_error.msg.clone());
|
||||||
err.code(code);
|
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_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);
|
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
|
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(
|
fn suggest_at_operator_in_slice_pat_with_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
err: &mut Diag<'_>,
|
err: &mut Diag<'_>,
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
struct Website {
|
||||||
|
url: String,
|
||||||
|
title: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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`.
|
Loading…
Add table
Add a link
Reference in a new issue