Skip to content

Commit a08b6f6

Browse files
committed
Detect struct construction with private field in field with default
When trying to construct a struct that has a public field of a private type, suggest using `..` if that field has a default value. ``` error[E0603]: struct `Priv1` is private --> $DIR/non-exhaustive-ctor.rs:25:39 | LL | let _ = S { field: (), field1: m::Priv1 {} }; | ------ ^^^^^ private struct | | | while setting this field | note: the struct `Priv1` is defined here --> $DIR/non-exhaustive-ctor.rs:14:4 | LL | struct Priv1 {} | ^^^^^^^^^^^^ help: the field `field1` you're trying to set has a default value, you can use `..` to use it | LL | let _ = S { field: (), .. }; | ~~ ```
1 parent e5e79f8 commit a08b6f6

File tree

11 files changed

+290
-36
lines changed

11 files changed

+290
-36
lines changed

compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,7 @@ provide! { tcx, def_id, other, cdata,
413413

414414
crate_extern_paths => { cdata.source().paths().cloned().collect() }
415415
expn_that_defined => { cdata.get_expn_that_defined(def_id.index, tcx.sess) }
416+
default_field => { cdata.get_default_field(def_id.index) }
416417
is_doc_hidden => { cdata.get_attr_flags(def_id.index).contains(AttrFlags::IS_DOC_HIDDEN) }
417418
doc_link_resolutions => { tcx.arena.alloc(cdata.get_doc_link_resolutions(def_id.index)) }
418419
doc_link_traits_in_scope => {

compiler/rustc_middle/src/query/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,13 @@ rustc_queries! {
18631863
feedable
18641864
}
18651865

1866+
/// Returns whether the impl or associated function has the `default` keyword.
1867+
query default_field(def_id: DefId) -> Option<DefId> {
1868+
desc { |tcx| "looking up the `const` corresponding to the default for `{}`", tcx.def_path_str(def_id) }
1869+
separate_provide_extern
1870+
feedable
1871+
}
1872+
18661873
query check_well_formed(key: LocalDefId) -> Result<(), ErrorGuaranteed> {
18671874
desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key) }
18681875
return_result_from_ensure_ok

compiler/rustc_resolve/src/build_reduced_graph.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -420,14 +420,18 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
420420
// The fields are not expanded yet.
421421
return;
422422
}
423-
let fields = fields
423+
let field_name = |i, field: &ast::FieldDef| {
424+
field.ident.unwrap_or_else(|| Ident::from_str_and_span(&format!("{i}"), field.span))
425+
};
426+
let field_names: Vec<_> =
427+
fields.iter().enumerate().map(|(i, field)| field_name(i, field)).collect();
428+
let defaults = fields
424429
.iter()
425430
.enumerate()
426-
.map(|(i, field)| {
427-
field.ident.unwrap_or_else(|| Ident::from_str_and_span(&format!("{i}"), field.span))
428-
})
431+
.filter_map(|(i, field)| field.default.as_ref().map(|_| field_name(i, field).name))
429432
.collect();
430-
self.r.field_names.insert(def_id, fields);
433+
self.r.field_names.insert(def_id, field_names);
434+
self.r.field_defaults.insert(def_id, defaults);
431435
}
432436

433437
fn insert_field_visibilities_local(&mut self, def_id: DefId, fields: &[ast::FieldDef]) {

compiler/rustc_resolve/src/diagnostics.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1946,8 +1946,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
19461946
}
19471947

19481948
fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) {
1949-
let PrivacyError { ident, binding, outermost_res, parent_scope, single_nested, dedup_span } =
1950-
*privacy_error;
1949+
let PrivacyError {
1950+
ident,
1951+
binding,
1952+
outermost_res,
1953+
parent_scope,
1954+
single_nested,
1955+
dedup_span,
1956+
ref source,
1957+
} = *privacy_error;
19511958

19521959
let res = binding.res();
19531960
let ctor_fields_span = self.ctor_fields_span(binding);
@@ -1963,6 +1970,55 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
19631970
let mut err =
19641971
self.dcx().create_err(errors::IsPrivate { span: ident.span, ident_descr, ident });
19651972

1973+
if let Some(expr) = source
1974+
&& let ast::ExprKind::Struct(struct_expr) = &expr.kind
1975+
&& let Some(Res::Def(_, def_id)) = self.partial_res_map
1976+
[&struct_expr.path.segments.iter().last().unwrap().id]
1977+
.full_res()
1978+
&& let Some(default_fields) = self.field_defaults(def_id)
1979+
&& !struct_expr.fields.is_empty()
1980+
{
1981+
let last_span = struct_expr.fields.iter().last().unwrap().span;
1982+
let mut iter = struct_expr.fields.iter().peekable();
1983+
let mut prev: Option<Span> = None;
1984+
while let Some(field) = iter.next() {
1985+
if field.expr.span.overlaps(ident.span) {
1986+
err.span_label(field.ident.span, "while setting this field");
1987+
if default_fields.contains(&field.ident.name) {
1988+
let sugg = if last_span == field.span {
1989+
vec![(field.span, "..".to_string())]
1990+
} else {
1991+
vec![
1992+
(
1993+
// Account for trailing commas and ensure we remove them.
1994+
match (prev, iter.peek()) {
1995+
(_, Some(next)) => field.span.with_hi(next.span.lo()),
1996+
(Some(prev), _) => field.span.with_lo(prev.hi()),
1997+
(None, None) => field.span,
1998+
},
1999+
String::new(),
2000+
),
2001+
(last_span.shrink_to_hi(), ", ..".to_string()),
2002+
]
2003+
};
2004+
err.multipart_suggestion_verbose(
2005+
format!(
2006+
"the type `{ident}` of field `{}` is private, but you can \
2007+
construct the default value defined for it in `{}` using `..` in \
2008+
the struct initializer expression",
2009+
field.ident,
2010+
self.tcx.item_name(def_id),
2011+
),
2012+
sugg,
2013+
Applicability::MachineApplicable,
2014+
);
2015+
break;
2016+
}
2017+
}
2018+
prev = Some(field.span);
2019+
}
2020+
}
2021+
19662022
let mut not_publicly_reexported = false;
19672023
if let Some((this_res, outer_ident)) = outermost_res {
19682024
let import_suggestions = self.lookup_import_candidates(

compiler/rustc_resolve/src/ident.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1010,6 +1010,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
10101010
binding,
10111011
dedup_span: path_span,
10121012
outermost_res: None,
1013+
source: None,
10131014
parent_scope: *parent_scope,
10141015
single_nested: path_span != root_span,
10151016
});
@@ -1416,7 +1417,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14161417
parent_scope: &ParentScope<'ra>,
14171418
ignore_import: Option<Import<'ra>>,
14181419
) -> PathResult<'ra> {
1419-
self.resolve_path_with_ribs(path, opt_ns, parent_scope, None, None, None, ignore_import)
1420+
self.resolve_path_with_ribs(
1421+
path,
1422+
opt_ns,
1423+
parent_scope,
1424+
None,
1425+
None,
1426+
None,
1427+
None,
1428+
ignore_import,
1429+
)
14201430
}
14211431

14221432
#[instrument(level = "debug", skip(self))]
@@ -1433,6 +1443,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14331443
path,
14341444
opt_ns,
14351445
parent_scope,
1446+
None,
14361447
finalize,
14371448
None,
14381449
ignore_binding,
@@ -1445,6 +1456,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
14451456
path: &[Segment],
14461457
opt_ns: Option<Namespace>, // `None` indicates a module path in import
14471458
parent_scope: &ParentScope<'ra>,
1459+
source: Option<PathSource<'_, '_, '_>>,
14481460
finalize: Option<Finalize>,
14491461
ribs: Option<&PerNS<Vec<Rib<'ra>>>>,
14501462
ignore_binding: Option<NameBinding<'ra>>,
@@ -1620,6 +1632,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
16201632
// the user it is not accessible.
16211633
for error in &mut self.privacy_errors[privacy_errors_len..] {
16221634
error.outermost_res = Some((res, ident));
1635+
error.source = match source {
1636+
Some(PathSource::Struct(Some(expr)))
1637+
| Some(PathSource::Expr(Some(expr))) => Some(expr.clone()),
1638+
_ => None,
1639+
};
16231640
}
16241641

16251642
let maybe_assoc = opt_ns != Some(MacroNS) && PathSource::Type.is_expected(res);

compiler/rustc_resolve/src/late.rs

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ pub(crate) enum PathSource<'a, 'ast, 'ra> {
425425
/// Paths in path patterns `Path`.
426426
Pat,
427427
/// Paths in struct expressions and patterns `Path { .. }`.
428-
Struct,
428+
Struct(Option<&'a Expr>),
429429
/// Paths in tuple struct patterns `Path(..)`.
430430
TupleStruct(Span, &'ra [Span]),
431431
/// `m::A::B` in `<T as m::A>::B::C`.
@@ -448,7 +448,7 @@ impl PathSource<'_, '_, '_> {
448448
match self {
449449
PathSource::Type
450450
| PathSource::Trait(_)
451-
| PathSource::Struct
451+
| PathSource::Struct(_)
452452
| PathSource::DefineOpaques => TypeNS,
453453
PathSource::Expr(..)
454454
| PathSource::Pat
@@ -465,7 +465,7 @@ impl PathSource<'_, '_, '_> {
465465
PathSource::Type
466466
| PathSource::Expr(..)
467467
| PathSource::Pat
468-
| PathSource::Struct
468+
| PathSource::Struct(_)
469469
| PathSource::TupleStruct(..)
470470
| PathSource::ReturnTypeNotation => true,
471471
PathSource::Trait(_)
@@ -482,7 +482,7 @@ impl PathSource<'_, '_, '_> {
482482
PathSource::Type => "type",
483483
PathSource::Trait(_) => "trait",
484484
PathSource::Pat => "unit struct, unit variant or constant",
485-
PathSource::Struct => "struct, variant or union type",
485+
PathSource::Struct(_) => "struct, variant or union type",
486486
PathSource::TraitItem(ValueNS, PathSource::TupleStruct(..))
487487
| PathSource::TupleStruct(..) => "tuple struct or tuple variant",
488488
PathSource::TraitItem(ns, _) => match ns {
@@ -577,7 +577,7 @@ impl PathSource<'_, '_, '_> {
577577
|| matches!(res, Res::Def(DefKind::Const | DefKind::AssocConst, _))
578578
}
579579
PathSource::TupleStruct(..) => res.expected_in_tuple_struct_pat(),
580-
PathSource::Struct => matches!(
580+
PathSource::Struct(_) => matches!(
581581
res,
582582
Res::Def(
583583
DefKind::Struct
@@ -617,8 +617,8 @@ impl PathSource<'_, '_, '_> {
617617
(PathSource::Trait(_), false) => E0405,
618618
(PathSource::Type | PathSource::DefineOpaques, true) => E0573,
619619
(PathSource::Type | PathSource::DefineOpaques, false) => E0412,
620-
(PathSource::Struct, true) => E0574,
621-
(PathSource::Struct, false) => E0422,
620+
(PathSource::Struct(_), true) => E0574,
621+
(PathSource::Struct(_), false) => E0422,
622622
(PathSource::Expr(..), true) | (PathSource::Delegation, true) => E0423,
623623
(PathSource::Expr(..), false) | (PathSource::Delegation, false) => E0425,
624624
(PathSource::Pat | PathSource::TupleStruct(..), true) => E0532,
@@ -1483,11 +1483,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
14831483
path: &[Segment],
14841484
opt_ns: Option<Namespace>, // `None` indicates a module path in import
14851485
finalize: Option<Finalize>,
1486+
source: PathSource<'_, 'ast, 'ra>,
14861487
) -> PathResult<'ra> {
14871488
self.r.resolve_path_with_ribs(
14881489
path,
14891490
opt_ns,
14901491
&self.parent_scope,
1492+
Some(source),
14911493
finalize,
14921494
Some(&self.ribs),
14931495
None,
@@ -1967,7 +1969,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
19671969
&mut self,
19681970
partial_res: PartialRes,
19691971
path: &[Segment],
1970-
source: PathSource<'_, '_, '_>,
1972+
source: PathSource<'_, 'ast, 'ra>,
19711973
path_span: Span,
19721974
) {
19731975
let proj_start = path.len() - partial_res.unresolved_segments();
@@ -2020,7 +2022,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
20202022
| PathSource::ReturnTypeNotation => false,
20212023
PathSource::Expr(..)
20222024
| PathSource::Pat
2023-
| PathSource::Struct
2025+
| PathSource::Struct(_)
20242026
| PathSource::TupleStruct(..)
20252027
| PathSource::DefineOpaques
20262028
| PathSource::Delegation => true,
@@ -3867,7 +3869,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
38673869
self.smart_resolve_path(pat.id, qself, path, PathSource::Pat);
38683870
}
38693871
PatKind::Struct(ref qself, ref path, ref _fields, ref rest) => {
3870-
self.smart_resolve_path(pat.id, qself, path, PathSource::Struct);
3872+
self.smart_resolve_path(pat.id, qself, path, PathSource::Struct(None));
38713873
self.record_patterns_with_skipped_bindings(pat, rest);
38723874
}
38733875
PatKind::Or(ref ps) => {
@@ -4111,7 +4113,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
41114113
id: NodeId,
41124114
qself: &Option<P<QSelf>>,
41134115
path: &Path,
4114-
source: PathSource<'_, 'ast, '_>,
4116+
source: PathSource<'_, 'ast, 'ra>,
41154117
) {
41164118
self.smart_resolve_path_fragment(
41174119
qself,
@@ -4128,7 +4130,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
41284130
&mut self,
41294131
qself: &Option<P<QSelf>>,
41304132
path: &[Segment],
4131-
source: PathSource<'_, 'ast, '_>,
4133+
source: PathSource<'_, 'ast, 'ra>,
41324134
finalize: Finalize,
41334135
record_partial_res: RecordPartialRes,
41344136
parent_qself: Option<&QSelf>,
@@ -4358,7 +4360,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
43584360
std_path.push(Segment::from_ident(Ident::with_dummy_span(sym::std)));
43594361
std_path.extend(path);
43604362
if let PathResult::Module(_) | PathResult::NonModule(_) =
4361-
self.resolve_path(&std_path, Some(ns), None)
4363+
self.resolve_path(&std_path, Some(ns), None, source)
43624364
{
43634365
// Check if we wrote `str::from_utf8` instead of `std::str::from_utf8`
43644366
let item_span =
@@ -4432,7 +4434,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
44324434
span: Span,
44334435
defer_to_typeck: bool,
44344436
finalize: Finalize,
4435-
source: PathSource<'_, 'ast, '_>,
4437+
source: PathSource<'_, 'ast, 'ra>,
44364438
) -> Result<Option<PartialRes>, Spanned<ResolutionError<'ra>>> {
44374439
let mut fin_res = None;
44384440

@@ -4475,7 +4477,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
44754477
path: &[Segment],
44764478
ns: Namespace,
44774479
finalize: Finalize,
4478-
source: PathSource<'_, 'ast, '_>,
4480+
source: PathSource<'_, 'ast, 'ra>,
44794481
) -> Result<Option<PartialRes>, Spanned<ResolutionError<'ra>>> {
44804482
debug!(
44814483
"resolve_qpath(qself={:?}, path={:?}, ns={:?}, finalize={:?})",
@@ -4538,7 +4540,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
45384540
)));
45394541
}
45404542

4541-
let result = match self.resolve_path(path, Some(ns), Some(finalize)) {
4543+
let result = match self.resolve_path(path, Some(ns), Some(finalize), source) {
45424544
PathResult::NonModule(path_res) => path_res,
45434545
PathResult::Module(ModuleOrUniformRoot::Module(module)) if !module.is_normal() => {
45444546
PartialRes::new(module.res().unwrap())
@@ -4761,7 +4763,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
47614763
}
47624764

47634765
ExprKind::Struct(ref se) => {
4764-
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct);
4766+
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct(parent));
47654767
// This is the same as `visit::walk_expr(self, expr);`, but we want to pass the
47664768
// parent in for accurate suggestions when encountering `Foo { bar }` that should
47674769
// have been `Foo { bar: self.bar }`.

0 commit comments

Comments
 (0)