Skip to content

Commit 0f82cff

Browse files
committed
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.
1 parent 21fe748 commit 0f82cff

File tree

8 files changed

+158
-7
lines changed

8 files changed

+158
-7
lines changed

compiler/rustc_ast/src/ast.rs

+2
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,8 @@ pub enum PatKind {
859859
pub enum PatFieldsRest {
860860
/// `module::StructName { field, ..}`
861861
Rest,
862+
/// `module::StructName { field, syntax error }`
863+
Recovered(ErrorGuaranteed),
862864
/// `module::StructName { field }`
863865
None,
864866
}

compiler/rustc_ast_lowering/src/pat.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,14 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
9292
span: self.lower_span(f.span),
9393
}
9494
}));
95-
break hir::PatKind::Struct(qpath, fs, *etc == ast::PatFieldsRest::Rest);
95+
break hir::PatKind::Struct(
96+
qpath,
97+
fs,
98+
matches!(
99+
etc,
100+
ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_)
101+
),
102+
);
96103
}
97104
PatKind::Tuple(pats) => {
98105
let (pats, ddpos) = self.lower_pat_tuple(pats, "tuple");

compiler/rustc_ast_pretty/src/pprust/state.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -1653,11 +1653,14 @@ impl<'a> State<'a> {
16531653
},
16541654
|f| f.pat.span,
16551655
);
1656-
if *etc == ast::PatFieldsRest::Rest {
1656+
if let ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) = etc {
16571657
if !fields.is_empty() {
16581658
self.word_space(",");
16591659
}
16601660
self.word("..");
1661+
if let ast::PatFieldsRest::Recovered(_) = etc {
1662+
self.word("/* recovered parse error */");
1663+
}
16611664
}
16621665
if !empty {
16631666
self.space();

compiler/rustc_parse/src/parser/pat.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1371,10 +1371,10 @@ impl<'a> Parser<'a> {
13711371
self.bump();
13721372
let (fields, etc) = self.parse_pat_fields().unwrap_or_else(|mut e| {
13731373
e.span_label(path.span, "while parsing the fields for this pattern");
1374-
e.emit();
1374+
let guar = e.emit();
13751375
self.recover_stmt();
13761376
// When recovering, pretend we had `Foo { .. }`, to avoid cascading errors.
1377-
(ThinVec::new(), PatFieldsRest::Rest)
1377+
(ThinVec::new(), PatFieldsRest::Recovered(guar))
13781378
});
13791379
self.bump();
13801380
Ok(PatKind::Struct(qself, path, fields, etc))

compiler/rustc_resolve/src/late.rs

+30-3
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,17 @@ impl RibKind<'_> {
264264
#[derive(Debug)]
265265
pub(crate) struct Rib<'ra, R = Res> {
266266
pub bindings: IdentMap<R>,
267+
pub patterns_with_skipped_bindings: FxHashMap<DefId, Vec<(Span, bool /* recovered error */)>>,
267268
pub kind: RibKind<'ra>,
268269
}
269270

270271
impl<'ra, R> Rib<'ra, R> {
271272
fn new(kind: RibKind<'ra>) -> Rib<'ra, R> {
272-
Rib { bindings: Default::default(), kind }
273+
Rib {
274+
bindings: Default::default(),
275+
patterns_with_skipped_bindings: Default::default(),
276+
kind,
277+
}
273278
}
274279
}
275280

@@ -3753,6 +3758,7 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
37533758
/// When a whole or-pattern has been dealt with, the thing happens.
37543759
///
37553760
/// See the implementation and `fresh_binding` for more details.
3761+
#[tracing::instrument(skip(self, bindings), level = "debug")]
37563762
fn resolve_pattern_inner(
37573763
&mut self,
37583764
pat: &Pat,
@@ -3761,7 +3767,6 @@ impl<'a, 'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
37613767
) {
37623768
// Visit all direct subpatterns of this pattern.
37633769
pat.walk(&mut |pat| {
3764-
debug!("resolve_pattern pat={:?} node={:?}", pat, pat.kind);
37653770
match pat.kind {
37663771
PatKind::Ident(bmode, ident, ref sub) => {
37673772
// 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> {
37873792
PatKind::Path(ref qself, ref path) => {
37883793
self.smart_resolve_path(pat.id, qself, path, PathSource::Pat);
37893794
}
3790-
PatKind::Struct(ref qself, ref path, ..) => {
3795+
PatKind::Struct(ref qself, ref path, ref _fields, ref rest) => {
37913796
self.smart_resolve_path(pat.id, qself, path, PathSource::Struct);
3797+
self.record_patterns_with_skipped_bindings(pat, rest);
37923798
}
37933799
PatKind::Or(ref ps) => {
37943800
// 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> {
38213827
});
38223828
}
38233829

3830+
fn record_patterns_with_skipped_bindings(&mut self, pat: &Pat, rest: &ast::PatFieldsRest) {
3831+
match rest {
3832+
ast::PatFieldsRest::Rest | ast::PatFieldsRest::Recovered(_) => {
3833+
// Record that the pattern doesn't introduce all the bindings it could.
3834+
if let Some(partial_res) = self.r.partial_res_map.get(&pat.id)
3835+
&& let Some(res) = partial_res.full_res()
3836+
&& let Some(def_id) = res.opt_def_id()
3837+
{
3838+
self.ribs[ValueNS]
3839+
.last_mut()
3840+
.unwrap()
3841+
.patterns_with_skipped_bindings
3842+
.entry(def_id)
3843+
.or_default()
3844+
.push((pat.span, matches!(rest, ast::PatFieldsRest::Recovered(_))));
3845+
}
3846+
}
3847+
ast::PatFieldsRest::None => {}
3848+
}
3849+
}
3850+
38243851
fn fresh_binding(
38253852
&mut self,
38263853
ident: Ident,

compiler/rustc_resolve/src/late/diagnostics.rs

+60
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
430430
let mut err = self.r.dcx().struct_span_err(base_error.span, base_error.msg.clone());
431431
err.code(code);
432432

433+
self.detect_missing_binding_available_from_pattern(&mut err, path, following_seg);
433434
self.suggest_at_operator_in_slice_pat_with_range(&mut err, path);
434435
self.suggest_swapping_misplaced_self_ty_and_trait(&mut err, source, res, base_error.span);
435436

@@ -1120,6 +1121,65 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
11201121
true
11211122
}
11221123

1124+
fn detect_missing_binding_available_from_pattern(
1125+
&mut self,
1126+
err: &mut Diag<'_>,
1127+
path: &[Segment],
1128+
following_seg: Option<&Segment>,
1129+
) {
1130+
let [segment] = path else { return };
1131+
let None = following_seg else { return };
1132+
'outer: for rib in self.ribs[ValueNS].iter().rev() {
1133+
for (def_id, spans) in &rib.patterns_with_skipped_bindings {
1134+
if let Some(fields) = self.r.field_idents(*def_id) {
1135+
for field in fields {
1136+
if field.name == segment.ident.name {
1137+
if spans.iter().all(|(_, was_recovered)| *was_recovered) {
1138+
// This resolution error will likely be fixed by fixing a
1139+
// syntax error in a pattern, so it is irrelevant to the user.
1140+
let multispan: MultiSpan =
1141+
spans.iter().map(|(s, _)| *s).collect::<Vec<_>>().into();
1142+
err.span_note(
1143+
multispan,
1144+
"this pattern had a recovered parse error which likely \
1145+
lost the expected fields",
1146+
);
1147+
err.downgrade_to_delayed_bug();
1148+
}
1149+
let mut multispan: MultiSpan = spans
1150+
.iter()
1151+
.filter(|(_, was_recovered)| !was_recovered)
1152+
.map(|(sp, _)| *sp)
1153+
.collect::<Vec<_>>()
1154+
.into();
1155+
let def_span = self.r.def_span(*def_id);
1156+
let ty = self.r.tcx.item_name(*def_id);
1157+
multispan.push_span_label(def_span, String::new());
1158+
multispan.push_span_label(field.span, "defined here".to_string());
1159+
for (span, _) in spans.iter().filter(|(_, r)| !r) {
1160+
multispan.push_span_label(
1161+
*span,
1162+
format!(
1163+
"this pattern doesn't include `{field}`, which is \
1164+
available in `{ty}`",
1165+
),
1166+
);
1167+
}
1168+
err.span_note(
1169+
multispan,
1170+
format!(
1171+
"`{ty}` has a field `{field}` which could have been included \
1172+
in this pattern, but it wasn't",
1173+
),
1174+
);
1175+
break 'outer;
1176+
}
1177+
}
1178+
}
1179+
}
1180+
}
1181+
}
1182+
11231183
fn suggest_at_operator_in_slice_pat_with_range(
11241184
&mut self,
11251185
err: &mut Diag<'_>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
struct Website {
2+
url: String,
3+
title: Option<String> ,//~ NOTE defined here
4+
}
5+
6+
fn main() {
7+
let website = Website {
8+
url: "http://www.example.com".into(),
9+
title: Some("Example Domain".into()),
10+
};
11+
12+
if let Website { url, Some(title) } = website { //~ ERROR expected `,`
13+
//~^ NOTE while parsing the fields for this pattern
14+
println!("[{}]({})", title, url); // we hide the errors for `title` and `url`
15+
}
16+
17+
if let Website { url, .. } = website { //~ NOTE `Website` has a field `title`
18+
//~^ NOTE this pattern
19+
println!("[{}]({})", title, url); //~ ERROR cannot find value `title` in this scope
20+
//~^ NOTE not found in this scope
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
error: expected `,`
2+
--> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:12:31
3+
|
4+
LL | if let Website { url, Some(title) } = website {
5+
| ------- ^
6+
| |
7+
| while parsing the fields for this pattern
8+
9+
error[E0425]: cannot find value `title` in this scope
10+
--> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:19:30
11+
|
12+
LL | println!("[{}]({})", title, url);
13+
| ^^^^^ not found in this scope
14+
|
15+
note: `Website` has a field `title` which could have been included in this pattern, but it wasn't
16+
--> $DIR/struct-pattern-with-missing-fields-resolve-error.rs:17:12
17+
|
18+
LL | / struct Website {
19+
LL | | url: String,
20+
LL | | title: Option<String> ,
21+
| | ----- defined here
22+
LL | | }
23+
| |_-
24+
...
25+
LL | if let Website { url, .. } = website {
26+
| ^^^^^^^^^^^^^^^^^^^ this pattern doesn't include `title`, which is available in `Website`
27+
28+
error: aborting due to 2 previous errors
29+
30+
For more information about this error, try `rustc --explain E0425`.

0 commit comments

Comments
 (0)