Skip to content

Commit 409c141

Browse files
committed
typeck/pat: inaccessible private fields
This commit adjusts the missing field diagnostic logic for struct patterns in typeck to improve the diagnostic when the missing fields are inaccessible. Signed-off-by: David Wood <david@davidtw.co>
1 parent c0894e7 commit 409c141

File tree

4 files changed

+141
-9
lines changed

4 files changed

+141
-9
lines changed

compiler/rustc_typeck/src/check/pat.rs

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,8 +1078,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10781078
let mut unmentioned_fields = variant
10791079
.fields
10801080
.iter()
1081-
.map(|field| field.ident.normalize_to_macros_2_0())
1082-
.filter(|ident| !used_fields.contains_key(&ident))
1081+
.map(|field| (field, field.ident.normalize_to_macros_2_0()))
1082+
.filter(|(_, ident)| !used_fields.contains_key(&ident))
10831083
.collect::<Vec<_>>();
10841084

10851085
let inexistent_fields_err = if !(inexistent_fields.is_empty() || variant.is_recovered()) {
@@ -1110,7 +1110,19 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11101110
tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit();
11111111
}
11121112
} else if !etc && !unmentioned_fields.is_empty() {
1113-
unmentioned_err = Some(self.error_unmentioned_fields(pat, &unmentioned_fields));
1113+
let no_accessible_unmentioned_fields = unmentioned_fields
1114+
.iter()
1115+
.filter(|(field, _)| {
1116+
field.vis.is_accessible_from(tcx.parent_module(pat.hir_id).to_def_id(), tcx)
1117+
})
1118+
.next()
1119+
.is_none();
1120+
1121+
if no_accessible_unmentioned_fields {
1122+
unmentioned_err = Some(self.error_no_accessible_fields(pat, &fields));
1123+
} else {
1124+
unmentioned_err = Some(self.error_unmentioned_fields(pat, &unmentioned_fields));
1125+
}
11141126
}
11151127
match (inexistent_fields_err, unmentioned_err) {
11161128
(Some(mut i), Some(mut u)) => {
@@ -1173,7 +1185,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11731185
&self,
11741186
kind_name: &str,
11751187
inexistent_fields: &[Ident],
1176-
unmentioned_fields: &mut Vec<Ident>,
1188+
unmentioned_fields: &mut Vec<(&ty::FieldDef, Ident)>,
11771189
variant: &ty::VariantDef,
11781190
) -> DiagnosticBuilder<'tcx> {
11791191
let tcx = self.tcx;
@@ -1215,7 +1227,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12151227
),
12161228
);
12171229
if plural == "" {
1218-
let input = unmentioned_fields.iter().map(|field| &field.name);
1230+
let input = unmentioned_fields.iter().map(|(_, field)| &field.name);
12191231
let suggested_name = find_best_match_for_name(input, ident.name, None);
12201232
if let Some(suggested_name) = suggested_name {
12211233
err.span_suggestion(
@@ -1232,7 +1244,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12321244
// `smart_resolve_context_dependent_help`.
12331245
if suggested_name.to_ident_string().parse::<usize>().is_err() {
12341246
// We don't want to throw `E0027` in case we have thrown `E0026` for them.
1235-
unmentioned_fields.retain(|&x| x.name != suggested_name);
1247+
unmentioned_fields.retain(|&(_, x)| x.name != suggested_name);
12361248
}
12371249
}
12381250
}
@@ -1300,17 +1312,77 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
13001312
None
13011313
}
13021314

1315+
/// Returns a diagnostic reporting a struct pattern which is missing an `..` due to
1316+
/// inaccessible fields.
1317+
///
1318+
/// ```ignore (diagnostic)
1319+
/// error: pattern requires `..` due to inaccessible fields
1320+
/// --> src/main.rs:10:9
1321+
/// |
1322+
/// LL | let foo::Foo {} = foo::Foo::default();
1323+
/// | ^^^^^^^^^^^
1324+
/// |
1325+
/// help: add a `..`
1326+
/// |
1327+
/// LL | let foo::Foo { .. } = foo::Foo::default();
1328+
/// | ^^^^^^
1329+
/// ```
1330+
fn error_no_accessible_fields(
1331+
&self,
1332+
pat: &Pat<'_>,
1333+
fields: &'tcx [hir::FieldPat<'tcx>],
1334+
) -> DiagnosticBuilder<'tcx> {
1335+
let mut err = self
1336+
.tcx
1337+
.sess
1338+
.struct_span_err(pat.span, "pattern requires `..` due to inaccessible fields");
1339+
1340+
if let Some(field) = fields.last() {
1341+
err.span_suggestion_verbose(
1342+
field.span.shrink_to_hi(),
1343+
"ignore the inaccessible and unused fields",
1344+
", ..".to_string(),
1345+
Applicability::MachineApplicable,
1346+
);
1347+
} else {
1348+
let qpath_span = if let PatKind::Struct(qpath, ..) = &pat.kind {
1349+
qpath.span()
1350+
} else {
1351+
bug!("`error_no_accessible_fields` called on non-struct pattern");
1352+
};
1353+
1354+
// Shrink the span to exclude the `foo:Foo` in `foo::Foo { }`.
1355+
let span = pat.span.with_lo(qpath_span.shrink_to_hi().hi());
1356+
err.span_suggestion_verbose(
1357+
span,
1358+
"ignore the inaccessible and unused fields",
1359+
" { .. }".to_string(),
1360+
Applicability::MachineApplicable,
1361+
);
1362+
}
1363+
err
1364+
}
1365+
1366+
/// Returns a diagnostic reporting a struct pattern which does not mention some fields.
1367+
///
1368+
/// ```ignore (diagnostic)
1369+
/// error[E0027]: pattern does not mention field `you_cant_use_this_field`
1370+
/// --> src/main.rs:15:9
1371+
/// |
1372+
/// LL | let foo::Foo {} = foo::Foo::new();
1373+
/// | ^^^^^^^^^^^ missing field `you_cant_use_this_field`
1374+
/// ```
13031375
fn error_unmentioned_fields(
13041376
&self,
13051377
pat: &Pat<'_>,
1306-
unmentioned_fields: &[Ident],
1378+
unmentioned_fields: &[(&ty::FieldDef, Ident)],
13071379
) -> DiagnosticBuilder<'tcx> {
13081380
let field_names = if unmentioned_fields.len() == 1 {
1309-
format!("field `{}`", unmentioned_fields[0])
1381+
format!("field `{}`", unmentioned_fields[0].1)
13101382
} else {
13111383
let fields = unmentioned_fields
13121384
.iter()
1313-
.map(|name| format!("`{}`", name))
1385+
.map(|(_, name)| format!("`{}`", name))
13141386
.collect::<Vec<String>>()
13151387
.join(", ");
13161388
format!("fields {}", fields)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
#![allow(dead_code, unused_variables)]
3+
4+
pub mod foo {
5+
#[derive(Default)]
6+
pub struct Foo { invisible: bool, }
7+
8+
#[derive(Default)]
9+
pub struct Bar { pub visible: bool, invisible: bool, }
10+
}
11+
12+
fn main() {
13+
let foo::Foo { .. } = foo::Foo::default();
14+
//~^ ERROR pattern requires `..` due to inaccessible fields
15+
16+
let foo::Bar { visible, .. } = foo::Bar::default();
17+
//~^ ERROR pattern requires `..` due to inaccessible fields
18+
}

src/test/ui/issues/issue-76077-1.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// run-rustfix
2+
#![allow(dead_code, unused_variables)]
3+
4+
pub mod foo {
5+
#[derive(Default)]
6+
pub struct Foo { invisible: bool, }
7+
8+
#[derive(Default)]
9+
pub struct Bar { pub visible: bool, invisible: bool, }
10+
}
11+
12+
fn main() {
13+
let foo::Foo {} = foo::Foo::default();
14+
//~^ ERROR pattern requires `..` due to inaccessible fields
15+
16+
let foo::Bar { visible } = foo::Bar::default();
17+
//~^ ERROR pattern requires `..` due to inaccessible fields
18+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: pattern requires `..` due to inaccessible fields
2+
--> $DIR/issue-76077-1.rs:13:9
3+
|
4+
LL | let foo::Foo {} = foo::Foo::default();
5+
| ^^^^^^^^^^^
6+
|
7+
help: ignore the inaccessible and unused fields
8+
|
9+
LL | let foo::Foo { .. } = foo::Foo::default();
10+
| ^^^^^^
11+
12+
error: pattern requires `..` due to inaccessible fields
13+
--> $DIR/issue-76077-1.rs:16:9
14+
|
15+
LL | let foo::Bar { visible } = foo::Bar::default();
16+
| ^^^^^^^^^^^^^^^^^^^^
17+
|
18+
help: ignore the inaccessible and unused fields
19+
|
20+
LL | let foo::Bar { visible, .. } = foo::Bar::default();
21+
| ^^^^
22+
23+
error: aborting due to 2 previous errors
24+

0 commit comments

Comments
 (0)