Skip to content

Commit 9f8a782

Browse files
authored
Rollup merge of rust-lang#76524 - davidtwco:issue-76077-inaccessible-private-fields, r=estebank
typeck: don't suggest inaccessible private fields Fixes rust-lang#76077. This PR adjusts the missing field diagnostic logic in typeck so that when none of the missing fields in a struct expr are accessible then the error is less confusing. r? @estebank
2 parents 7565ccc + 409c141 commit 9f8a782

File tree

7 files changed

+244
-42
lines changed

7 files changed

+244
-42
lines changed

compiler/rustc_typeck/src/check/expr.rs

+85-33
Original file line numberDiff line numberDiff line change
@@ -1241,42 +1241,21 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12411241
tcx.sess.span_err(span, "union expressions should have exactly one field");
12421242
}
12431243
} else if check_completeness && !error_happened && !remaining_fields.is_empty() {
1244-
let len = remaining_fields.len();
1245-
1246-
let mut displayable_field_names =
1247-
remaining_fields.keys().map(|ident| ident.as_str()).collect::<Vec<_>>();
1248-
1249-
displayable_field_names.sort();
1244+
let no_accessible_remaining_fields = remaining_fields
1245+
.iter()
1246+
.filter(|(_, (_, field))| {
1247+
field.vis.is_accessible_from(tcx.parent_module(expr_id).to_def_id(), tcx)
1248+
})
1249+
.next()
1250+
.is_none();
12501251

1251-
let truncated_fields_error = if len <= 3 {
1252-
String::new()
1252+
if no_accessible_remaining_fields {
1253+
self.report_no_accessible_fields(adt_ty, span);
12531254
} else {
1254-
format!(" and {} other field{}", (len - 3), if len - 3 == 1 { "" } else { "s" })
1255-
};
1256-
1257-
let remaining_fields_names = displayable_field_names
1258-
.iter()
1259-
.take(3)
1260-
.map(|n| format!("`{}`", n))
1261-
.collect::<Vec<_>>()
1262-
.join(", ");
1263-
1264-
struct_span_err!(
1265-
tcx.sess,
1266-
span,
1267-
E0063,
1268-
"missing field{} {}{} in initializer of `{}`",
1269-
pluralize!(remaining_fields.len()),
1270-
remaining_fields_names,
1271-
truncated_fields_error,
1272-
adt_ty
1273-
)
1274-
.span_label(
1275-
span,
1276-
format!("missing {}{}", remaining_fields_names, truncated_fields_error),
1277-
)
1278-
.emit();
1255+
self.report_missing_field(adt_ty, span, remaining_fields);
1256+
}
12791257
}
1258+
12801259
error_happened
12811260
}
12821261

@@ -1293,6 +1272,79 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12931272
}
12941273
}
12951274

1275+
/// Report an error for a struct field expression when there are fields which aren't provided.
1276+
///
1277+
/// ```ignore (diagnostic)
1278+
/// error: missing field `you_can_use_this_field` in initializer of `foo::Foo`
1279+
/// --> src/main.rs:8:5
1280+
/// |
1281+
/// 8 | foo::Foo {};
1282+
/// | ^^^^^^^^ missing `you_can_use_this_field`
1283+
///
1284+
/// error: aborting due to previous error
1285+
/// ```
1286+
fn report_missing_field(
1287+
&self,
1288+
adt_ty: Ty<'tcx>,
1289+
span: Span,
1290+
remaining_fields: FxHashMap<Ident, (usize, &ty::FieldDef)>,
1291+
) {
1292+
let tcx = self.tcx;
1293+
let len = remaining_fields.len();
1294+
1295+
let mut displayable_field_names =
1296+
remaining_fields.keys().map(|ident| ident.as_str()).collect::<Vec<_>>();
1297+
1298+
displayable_field_names.sort();
1299+
1300+
let truncated_fields_error = if len <= 3 {
1301+
String::new()
1302+
} else {
1303+
format!(" and {} other field{}", (len - 3), if len - 3 == 1 { "" } else { "s" })
1304+
};
1305+
1306+
let remaining_fields_names = displayable_field_names
1307+
.iter()
1308+
.take(3)
1309+
.map(|n| format!("`{}`", n))
1310+
.collect::<Vec<_>>()
1311+
.join(", ");
1312+
1313+
struct_span_err!(
1314+
tcx.sess,
1315+
span,
1316+
E0063,
1317+
"missing field{} {}{} in initializer of `{}`",
1318+
pluralize!(remaining_fields.len()),
1319+
remaining_fields_names,
1320+
truncated_fields_error,
1321+
adt_ty
1322+
)
1323+
.span_label(span, format!("missing {}{}", remaining_fields_names, truncated_fields_error))
1324+
.emit();
1325+
}
1326+
1327+
/// Report an error for a struct field expression when there are no visible fields.
1328+
///
1329+
/// ```ignore (diagnostic)
1330+
/// error: cannot construct `Foo` with struct literal syntax due to inaccessible fields
1331+
/// --> src/main.rs:8:5
1332+
/// |
1333+
/// 8 | foo::Foo {};
1334+
/// | ^^^^^^^^
1335+
///
1336+
/// error: aborting due to previous error
1337+
/// ```
1338+
fn report_no_accessible_fields(&self, adt_ty: Ty<'tcx>, span: Span) {
1339+
self.tcx.sess.span_err(
1340+
span,
1341+
&format!(
1342+
"cannot construct `{}` with struct literal syntax due to inaccessible fields",
1343+
adt_ty,
1344+
),
1345+
);
1346+
}
1347+
12961348
fn report_unknown_field(
12971349
&self,
12981350
ty: Ty<'tcx>,

compiler/rustc_typeck/src/check/pat.rs

+81-9
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)
+18
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

+18
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+
}
+24
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+

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

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pub mod foo {
2+
pub struct Foo {
3+
you_cant_use_this_field: bool,
4+
}
5+
}
6+
7+
fn main() {
8+
foo::Foo {};
9+
//~^ ERROR cannot construct `Foo` with struct literal syntax due to inaccessible fields
10+
}

src/test/ui/issues/issue-76077.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
error: cannot construct `Foo` with struct literal syntax due to inaccessible fields
2+
--> $DIR/issue-76077.rs:8:5
3+
|
4+
LL | foo::Foo {};
5+
| ^^^^^^^^
6+
7+
error: aborting due to previous error
8+

0 commit comments

Comments
 (0)