diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2025-01-21 21:07:20 +0000 |
|---|---|---|
| committer | Esteban Küber <esteban@kuber.com.ar> | 2025-08-10 19:15:18 +0000 |
| commit | 464a6b1b4af28f7b2d1adf051bad3f182e23b88e (patch) | |
| tree | 33f8b9d8e302ee705b99b74923c63c20bc96b97e /compiler/rustc_resolve/src | |
| parent | 18eeac04fc5c2a4c4a8020dbdf1c652077ad0e4e (diff) | |
| download | rust-464a6b1b4af28f7b2d1adf051bad3f182e23b88e.tar.gz rust-464a6b1b4af28f7b2d1adf051bad3f182e23b88e.zip | |
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: (), .. };
| ~~
```
Diffstat (limited to 'compiler/rustc_resolve/src')
| -rw-r--r-- | compiler/rustc_resolve/src/build_reduced_graph.rs | 14 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/diagnostics.rs | 60 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/ident.rs | 19 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late.rs | 36 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/late/diagnostics.rs | 27 | ||||
| -rw-r--r-- | compiler/rustc_resolve/src/lib.rs | 18 |
6 files changed, 138 insertions, 36 deletions
diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 2ad8543bf8c..d18d0fc16a8 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -420,14 +420,18 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> { // The fields are not expanded yet. return; } - let fields = fields + let field_name = |i, field: &ast::FieldDef| { + field.ident.unwrap_or_else(|| Ident::from_str_and_span(&format!("{i}"), field.span)) + }; + let field_names: Vec<_> = + fields.iter().enumerate().map(|(i, field)| field_name(i, field)).collect(); + let defaults = fields .iter() .enumerate() - .map(|(i, field)| { - field.ident.unwrap_or_else(|| Ident::from_str_and_span(&format!("{i}"), field.span)) - }) + .filter_map(|(i, field)| field.default.as_ref().map(|_| field_name(i, field).name)) .collect(); - self.r.field_names.insert(def_id, fields); + self.r.field_names.insert(def_id, field_names); + self.r.field_defaults.insert(def_id, defaults); } fn insert_field_visibilities_local(&mut self, def_id: DefId, fields: &[ast::FieldDef]) { diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 66f79585d92..40ea90bf484 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1943,8 +1943,15 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } fn report_privacy_error(&mut self, privacy_error: &PrivacyError<'ra>) { - let PrivacyError { ident, binding, outermost_res, parent_scope, single_nested, dedup_span } = - *privacy_error; + let PrivacyError { + ident, + binding, + outermost_res, + parent_scope, + single_nested, + dedup_span, + ref source, + } = *privacy_error; let res = binding.res(); let ctor_fields_span = self.ctor_fields_span(binding); @@ -1960,6 +1967,55 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { let mut err = self.dcx().create_err(errors::IsPrivate { span: ident.span, ident_descr, ident }); + if let Some(expr) = source + && let ast::ExprKind::Struct(struct_expr) = &expr.kind + && let Some(Res::Def(_, def_id)) = self.partial_res_map + [&struct_expr.path.segments.iter().last().unwrap().id] + .full_res() + && let Some(default_fields) = self.field_defaults(def_id) + && !struct_expr.fields.is_empty() + { + let last_span = struct_expr.fields.iter().last().unwrap().span; + let mut iter = struct_expr.fields.iter().peekable(); + let mut prev: Option<Span> = None; + while let Some(field) = iter.next() { + if field.expr.span.overlaps(ident.span) { + err.span_label(field.ident.span, "while setting this field"); + if default_fields.contains(&field.ident.name) { + let sugg = if last_span == field.span { + vec![(field.span, "..".to_string())] + } else { + vec![ + ( + // Account for trailing commas and ensure we remove them. + match (prev, iter.peek()) { + (_, Some(next)) => field.span.with_hi(next.span.lo()), + (Some(prev), _) => field.span.with_lo(prev.hi()), + (None, None) => field.span, + }, + String::new(), + ), + (last_span.shrink_to_hi(), ", ..".to_string()), + ] + }; + err.multipart_suggestion_verbose( + format!( + "the type `{ident}` of field `{}` is private, but you can \ + construct the default value defined for it in `{}` using `..` in \ + the struct initializer expression", + field.ident, + self.tcx.item_name(def_id), + ), + sugg, + Applicability::MachineApplicable, + ); + break; + } + } + prev = Some(field.span); + } + } + let mut not_publicly_reexported = false; if let Some((this_res, outer_ident)) = outermost_res { let import_suggestions = self.lookup_import_candidates( diff --git a/compiler/rustc_resolve/src/ident.rs b/compiler/rustc_resolve/src/ident.rs index 092bb6fc4f9..9efcef695b7 100644 --- a/compiler/rustc_resolve/src/ident.rs +++ b/compiler/rustc_resolve/src/ident.rs @@ -1029,6 +1029,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { binding, dedup_span: path_span, outermost_res: None, + source: None, parent_scope: *parent_scope, single_nested: path_span != root_span, }); @@ -1435,7 +1436,16 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { parent_scope: &ParentScope<'ra>, ignore_import: Option<Import<'ra>>, ) -> PathResult<'ra> { - self.resolve_path_with_ribs(path, opt_ns, parent_scope, None, None, None, ignore_import) + self.resolve_path_with_ribs( + path, + opt_ns, + parent_scope, + None, + None, + None, + None, + ignore_import, + ) } #[instrument(level = "debug", skip(self))] pub(crate) fn resolve_path<'r>( @@ -1451,6 +1461,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path, opt_ns, parent_scope, + None, finalize, None, ignore_binding, @@ -1463,6 +1474,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { path: &[Segment], opt_ns: Option<Namespace>, // `None` indicates a module path in import parent_scope: &ParentScope<'ra>, + source: Option<PathSource<'_, '_, '_>>, finalize: Option<Finalize>, ribs: Option<&PerNS<Vec<Rib<'ra>>>>, ignore_binding: Option<NameBinding<'ra>>, @@ -1645,6 +1657,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { if finalize.is_some() { for error in &mut self.get_mut().privacy_errors[privacy_errors_len..] { error.outermost_res = Some((res, ident)); + error.source = match source { + Some(PathSource::Struct(Some(expr))) + | Some(PathSource::Expr(Some(expr))) => Some(expr.clone()), + _ => None, + }; } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 8f0c08c3998..953b72fd72b 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -424,7 +424,7 @@ pub(crate) enum PathSource<'a, 'ast, 'ra> { /// Paths in path patterns `Path`. Pat, /// Paths in struct expressions and patterns `Path { .. }`. - Struct, + Struct(Option<&'a Expr>), /// Paths in tuple struct patterns `Path(..)`. TupleStruct(Span, &'ra [Span]), /// `m::A::B` in `<T as m::A>::B::C`. @@ -447,7 +447,7 @@ impl PathSource<'_, '_, '_> { match self { PathSource::Type | PathSource::Trait(_) - | PathSource::Struct + | PathSource::Struct(_) | PathSource::DefineOpaques => TypeNS, PathSource::Expr(..) | PathSource::Pat @@ -464,7 +464,7 @@ impl PathSource<'_, '_, '_> { PathSource::Type | PathSource::Expr(..) | PathSource::Pat - | PathSource::Struct + | PathSource::Struct(_) | PathSource::TupleStruct(..) | PathSource::ReturnTypeNotation => true, PathSource::Trait(_) @@ -481,7 +481,7 @@ impl PathSource<'_, '_, '_> { PathSource::Type => "type", PathSource::Trait(_) => "trait", PathSource::Pat => "unit struct, unit variant or constant", - PathSource::Struct => "struct, variant or union type", + PathSource::Struct(_) => "struct, variant or union type", PathSource::TraitItem(ValueNS, PathSource::TupleStruct(..)) | PathSource::TupleStruct(..) => "tuple struct or tuple variant", PathSource::TraitItem(ns, _) => match ns { @@ -576,7 +576,7 @@ impl PathSource<'_, '_, '_> { || matches!(res, Res::Def(DefKind::Const | DefKind::AssocConst, _)) } PathSource::TupleStruct(..) => res.expected_in_tuple_struct_pat(), - PathSource::Struct => matches!( + PathSource::Struct(_) => matches!( res, Res::Def( DefKind::Struct @@ -616,8 +616,8 @@ impl PathSource<'_, '_, '_> { (PathSource::Trait(_), false) => E0405, (PathSource::Type | PathSource::DefineOpaques, true) => E0573, (PathSource::Type | PathSource::DefineOpaques, false) => E0412, - (PathSource::Struct, true) => E0574, - (PathSource::Struct, false) => E0422, + (PathSource::Struct(_), true) => E0574, + (PathSource::Struct(_), false) => E0422, (PathSource::Expr(..), true) | (PathSource::Delegation, true) => E0423, (PathSource::Expr(..), false) | (PathSource::Delegation, false) => E0425, (PathSource::Pat | PathSource::TupleStruct(..), true) => E0532, @@ -1482,11 +1482,13 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { path: &[Segment], opt_ns: Option<Namespace>, // `None` indicates a module path in import finalize: Option<Finalize>, + source: PathSource<'_, 'ast, 'ra>, ) -> PathResult<'ra> { self.r.cm().resolve_path_with_ribs( path, opt_ns, &self.parent_scope, + Some(source), finalize, Some(&self.ribs), None, @@ -1966,7 +1968,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { &mut self, partial_res: PartialRes, path: &[Segment], - source: PathSource<'_, '_, '_>, + source: PathSource<'_, 'ast, 'ra>, path_span: Span, ) { let proj_start = path.len() - partial_res.unresolved_segments(); @@ -2019,7 +2021,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { | PathSource::ReturnTypeNotation => false, PathSource::Expr(..) | PathSource::Pat - | PathSource::Struct + | PathSource::Struct(_) | PathSource::TupleStruct(..) | PathSource::DefineOpaques | PathSource::Delegation => true, @@ -3866,7 +3868,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.smart_resolve_path(pat.id, qself, path, PathSource::Pat); } 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(None)); self.record_patterns_with_skipped_bindings(pat, rest); } PatKind::Or(ref ps) => { @@ -4110,7 +4112,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { id: NodeId, qself: &Option<Box<QSelf>>, path: &Path, - source: PathSource<'_, 'ast, '_>, + source: PathSource<'_, 'ast, 'ra>, ) { self.smart_resolve_path_fragment( qself, @@ -4127,7 +4129,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { &mut self, qself: &Option<Box<QSelf>>, path: &[Segment], - source: PathSource<'_, 'ast, '_>, + source: PathSource<'_, 'ast, 'ra>, finalize: Finalize, record_partial_res: RecordPartialRes, parent_qself: Option<&QSelf>, @@ -4365,7 +4367,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { std_path.push(Segment::from_ident(Ident::with_dummy_span(sym::std))); std_path.extend(path); if let PathResult::Module(_) | PathResult::NonModule(_) = - self.resolve_path(&std_path, Some(ns), None) + self.resolve_path(&std_path, Some(ns), None, source) { // Check if we wrote `str::from_utf8` instead of `std::str::from_utf8` let item_span = @@ -4439,7 +4441,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { span: Span, defer_to_typeck: bool, finalize: Finalize, - source: PathSource<'_, 'ast, '_>, + source: PathSource<'_, 'ast, 'ra>, ) -> Result<Option<PartialRes>, Spanned<ResolutionError<'ra>>> { let mut fin_res = None; @@ -4488,7 +4490,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { path: &[Segment], ns: Namespace, finalize: Finalize, - source: PathSource<'_, 'ast, '_>, + source: PathSource<'_, 'ast, 'ra>, ) -> Result<Option<PartialRes>, Spanned<ResolutionError<'ra>>> { debug!( "resolve_qpath(qself={:?}, path={:?}, ns={:?}, finalize={:?})", @@ -4551,7 +4553,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { ))); } - let result = match self.resolve_path(path, Some(ns), Some(finalize)) { + let result = match self.resolve_path(path, Some(ns), Some(finalize), source) { PathResult::NonModule(path_res) => path_res, PathResult::Module(ModuleOrUniformRoot::Module(module)) if !module.is_normal() => { PartialRes::new(module.res().unwrap()) @@ -4774,7 +4776,7 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } ExprKind::Struct(ref se) => { - self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct); + self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct(parent)); // This is the same as `visit::walk_expr(self, expr);`, but we want to pass the // parent in for accurate suggestions when encountering `Foo { bar }` that should // have been `Foo { bar: self.bar }`. diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 0807142a7c3..aca251da71d 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -174,7 +174,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { &mut self, path: &[Segment], span: Span, - source: PathSource<'_, '_, '_>, + source: PathSource<'_, 'ast, 'ra>, res: Option<Res>, ) -> BaseError { // Make the base error. @@ -318,7 +318,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { (String::new(), "the crate root".to_string(), Some(CRATE_DEF_ID.to_def_id()), None) } else { let mod_path = &path[..path.len() - 1]; - let mod_res = self.resolve_path(mod_path, Some(TypeNS), None); + let mod_res = self.resolve_path(mod_path, Some(TypeNS), None, source); let mod_prefix = match mod_res { PathResult::Module(ModuleOrUniformRoot::Module(module)) => module.res(), _ => None, @@ -419,7 +419,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { path: &[Segment], following_seg: Option<&Segment>, span: Span, - source: PathSource<'_, '_, '_>, + source: PathSource<'_, 'ast, 'ra>, res: Option<Res>, qself: Option<&QSelf>, ) -> (Diag<'tcx>, Vec<ImportSuggestion>) { @@ -1014,7 +1014,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { fn suggest_typo( &mut self, err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, + source: PathSource<'_, 'ast, 'ra>, path: &[Segment], following_seg: Option<&Segment>, span: Span, @@ -1333,7 +1333,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { fn suggest_swapping_misplaced_self_ty_and_trait( &mut self, err: &mut Diag<'_>, - source: PathSource<'_, '_, '_>, + source: PathSource<'_, 'ast, 'ra>, res: Option<Res>, span: Span, ) { @@ -1341,7 +1341,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { self.diag_metadata.currently_processing_impl_trait.clone() && let TyKind::Path(_, self_ty_path) = &self_ty.kind && let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(&Segment::from_path(self_ty_path), Some(TypeNS), None) + self.resolve_path(&Segment::from_path(self_ty_path), Some(TypeNS), None, source) && let ModuleKind::Def(DefKind::Trait, ..) = module.kind && trait_ref.path.span == span && let PathSource::Trait(_) = source @@ -1449,13 +1449,13 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { fn get_single_associated_item( &mut self, path: &[Segment], - source: &PathSource<'_, '_, '_>, + source: &PathSource<'_, 'ast, 'ra>, filter_fn: &impl Fn(Res) -> bool, ) -> Option<TypoSuggestion> { if let crate::PathSource::TraitItem(_, _) = source { let mod_path = &path[..path.len() - 1]; if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(mod_path, None, None) + self.resolve_path(mod_path, None, None, *source) { let targets: Vec<_> = self .r @@ -1854,7 +1854,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { PathSource::Expr(Some(Expr { kind: ExprKind::Index(..) | ExprKind::Call(..), .. })) - | PathSource::Struct, + | PathSource::Struct(_), ) => { // Don't suggest macro if it's unstable. let suggestable = def_id.is_local() @@ -2502,7 +2502,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { // Search in module. let mod_path = &path[..path.len() - 1]; if let PathResult::Module(ModuleOrUniformRoot::Module(module)) = - self.resolve_path(mod_path, Some(TypeNS), None) + self.resolve_path(mod_path, Some(TypeNS), None, PathSource::Type) { self.r.add_module_candidates(module, &mut names, &filter_fn, None); } @@ -3673,7 +3673,12 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { if let TyKind::Path(None, path) = &ty.kind { // Check if the path being borrowed is likely to be owned. let path: Vec<_> = Segment::from_path(path); - match self.resolve_path(&path, Some(TypeNS), None) { + match self.resolve_path( + &path, + Some(TypeNS), + None, + PathSource::Type, + ) { PathResult::Module(ModuleOrUniformRoot::Module(module)) => { match module.res() { Some(Res::PrimTy(PrimTy::Str)) => { diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 23f44ff1658..b43f71913d9 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -822,6 +822,7 @@ struct PrivacyError<'ra> { parent_scope: ParentScope<'ra>, /// Is the format `use a::{b,c}`? single_nested: bool, + source: Option<ast::Expr>, } #[derive(Debug)] @@ -1064,6 +1065,7 @@ pub struct Resolver<'ra, 'tcx> { /// N.B., this is used only for better diagnostics, not name resolution itself. field_names: LocalDefIdMap<Vec<Ident>>, + field_defaults: LocalDefIdMap<Vec<Symbol>>, /// Span of the privacy modifier in fields of an item `DefId` accessible with dot syntax. /// Used for hints during error reporting. @@ -1538,6 +1540,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { extern_prelude, field_names: Default::default(), + field_defaults: Default::default(), field_visibility_spans: FxHashMap::default(), determined_imports: Vec::new(), @@ -2318,6 +2321,21 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { } } + fn field_defaults(&self, def_id: DefId) -> Option<Vec<Symbol>> { + match def_id.as_local() { + Some(def_id) => self.field_defaults.get(&def_id).cloned(), + None => Some( + self.tcx + .associated_item_def_ids(def_id) + .iter() + .filter_map(|&def_id| { + self.tcx.default_field(def_id).map(|_| self.tcx.item_name(def_id)) + }) + .collect(), + ), + } + } + /// Checks if an expression refers to a function marked with /// `#[rustc_legacy_const_generics]` and returns the argument index list /// from the attribute. |
