Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ fn register_builtins(store: &mut LintStore) {
UNUSED_VISIBILITIES,
UNUSED_ASSIGNMENTS,
DEAD_CODE,
UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS,
UNUSED_MUT,
// FIXME: add this lint when it becomes stable,
// see https://github.com/rust-lang/rust/issues/115585.
Expand Down
42 changes: 42 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ declare_lint_pass! {
UNUSED_MACRO_RULES,
UNUSED_MUT,
UNUSED_QUALIFICATIONS,
UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS,
UNUSED_UNSAFE,
UNUSED_VARIABLES,
UNUSED_VISIBILITIES,
Expand Down Expand Up @@ -818,6 +819,47 @@ declare_lint! {
crate_level_only
}

declare_lint! {
/// The `unused_unconstructable_pub_structs` lint detects public structs
/// with private fields that cannot be constructed or otherwise used through
/// the external API.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![deny(unused_unconstructable_pub_structs)]
///
/// pub struct Foo(i32);
/// # fn main() {}
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// This lint is emitted for a `pub` struct when:
///
/// * It has private fields and no public constructor, so downstream crates
/// cannot construct a value of the type.
/// * It is not used locally and does not appear in any reachable path from
/// external APIs other than the public struct item itself.

@Mark-Simulacrum Mark-Simulacrum Jun 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would think the "reachable path from external APIs" is what is ruling out this lint applying to code like this:

pub struct Bar(u32); // no lint
pub fn foo(_: Bar) {}

but then this code still lints and it seems very similar to the above -- Bar is mentioned in public APIs, albeit all impls on Bar:

#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Bar(u32); // yes lint

impl Copy for Bar {}
impl Clone for Bar {
        fn clone(&self) -> Self { todo!() }
}

and yet if one of those is an inherent impl, then the lint stops applying:

pub struct Bar(u32); // no lint
impl Bar { pub fn foo() {} }

or takes Bar as an argument:

pub struct Bar(u32); // no lint
impl From<Bar> for u32 {
        fn from(_: Bar) -> u32 { todo!() }
}

Can you say more about the reasoning behind why we're applying this lint in some cases but not others? It seems like the "non-constructable" argument ought to apply to all cases here.

View changes since the review

@mu001999 mu001999 Jun 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense to apply this to free functions and inherent methods whose signatures depend on that type.

The current implementation is based on methods' signatures have receivers or not. This means only methods like fn foo(&self) or with other kinds of receivers would be defered to analyze (based on the existing two-phase analysis for impls.).

For methods like impl Bar { pub fn foo() {} }, since the signature does not depend on Bar, we only make the most conservative assumption: the function body may use that type. Unless we pre-collect the defs that can be reached through that function body, but that would be a big change, and I guess it may cause a significant performance regression. The analysis here is wrong, see below.

For methods like impl From<Bar> for u32 { fn from(_: Bar) -> u32 { todo!() } }, this may be easy to extend, becasue we could not only check the method has a receiver, but also check if the self type appear in types of params.

For free functions or methods like pub fn foo(_: Bar) {}, I think this could be also extended. But this requires we extend the two-phase analysis for impls to free functions, and records the defs are depent by such functions, because we could have multi params, e.g., pub fn foo(_: Foo, _: Bar) {}.

I think these cases (partly) could be handled later as extensions to this lint, and implemented in separate PRs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we only make the most conservative assumption: the function body may use that type. Unless we pre-collect the defs that can be reached through that function body, but that would be a big change, and I guess it may cause a significant performance regression.

Don't we have to look through the body already? I'd expect that to be needed to determine that the local crate isn't constructing the structure. And indeed just mentioning the type wthin a function does silence this lint, even if the constructor is not actually invoked:

pub struct Bar(u32); // no lint

pub fn foo() {
        Bar;
}

@mu001999 mu001999 Jun 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion. After thinking about it more, I don’t think we could extend the lint to cases like impl Bar { pub fn foo() { ... } }.

If Bar::foo is public, then it will propagate to Bar, because Bar::foo depends on Bar (at type-level at least), regardless of whether its body actually uses Bar.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, but some external crate could also be depending on Bar at a type level. I think it still makes sense that if Bar doesn't look like a special type (heuristics you've put in the conditions to apply this lint under -- ZST/inhabited/etc), then we should tell the user that while the type is in the public API, it's a 'weird' pattern and quite plausibly a bug (e.g., missing API).

I don't see much difference between impl Bar { pub fn foo() {} } and pub fn foo(_: Option<Bar>) {}, personally. Both are callable without constructing Bar, but only one of them triggers the lint.

I'm also seeing that we don't silence the lint in the presence of default trait methods returning the otherwise-hidden struct, e.g. this lints:

pub struct Bar(u32);

pub trait Exposed: Sized {
    fn output() -> Self { todo!() }
}

impl Exposed for Bar { }

and this lints, which is fully incorrect:

pub struct Bar(u32);

pub trait Exposed: Sized {
    fn output() -> Self { todo!() }
}

trait Foo {
    fn output() -> Self;
}
impl Foo for Bar {
    fn output() -> Self { Bar(0) }
}

impl<T: Foo> Exposed for T {
        fn output() -> Self { T::output() }
}

I'm guessing it's pretty hard to fix these. In general I think the utility of this lint is not necessarily worth its complexity -- it seems like we need a pretty sophisticated analysis if we want to avoid potential false positives like this. The examples here are a little contrived, but the combination of complicated heuristics and the fact that it's a breaking change to remove the struct if you care about API stability makes me question whether it's a good idea to keep trying to fix the lint.

@mu001999 mu001999 Jun 7, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much difference between impl Bar { pub fn foo() {} } and pub fn foo(_: Option) {}, personally. Both are callable without constructing Bar, but only one of them triggers the lint.

They both won't trigger the lint because they both will mark Bar as live (for now). But seems what you said is mainly for the future.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing it's pretty hard to fix these. In general I think the utility of this lint is not necessarily worth its complexity -- it seems like we need a pretty sophisticated analysis if we want to avoid potential false positives like this. The examples here are a little contrived, but the combination of complicated heuristics and the fact that it's a breaking change to remove the struct if you care about API stability makes me question whether it's a good idea to keep trying to fix the lint.

After thinking it over, I agree with your point. I also haven’t come up with a low-complexity way to address the case above.

So the best approach may be to close this PR for now.

/// * It is not uninhabited, is not composed only of ZST fields, and has no unit field.

@Mark-Simulacrum Mark-Simulacrum Jun 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we exclude types with repr(C)/repr(transparent) on them as well?

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add the rule into the description

///
/// Such structs may have been unused for a long time, but are now effectively dead code.
/// This lint helps find those items.
///
/// To silence the warning for individual items, prefix the name with an
/// underscore such as `_Foo`.

@Mark-Simulacrum Mark-Simulacrum Jun 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the type is pre-existing, but can't just be deleted, then renaming it is also highly likely not possible (a breaking change as much as deleting it would be).

If the type is new then naming it _Foo seems like the wrong recommendation: either allowing the lint, adding a ! or () field, making it an empty enum (enum Foo {}) all seem like better options.

I'd prefer we recommend one of those options over the _ prefixing. I think we do that below ("is intentional"), maybe we just delete this line?

View changes since the review

///
/// To indicate that the behavior is intentional, add a field with unit or
/// never type if the struct is only used at the type level.
///
/// Otherwise, consider removing it if the struct is no longer in use.
pub UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS,

@Mark-Simulacrum Mark-Simulacrum Jun 6, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regards to the name and finding alternatives to unconstructable, how would we feel about focusing on the fields being dead code? Essentially something like unused_private_fields or so?

Alternatively, I think in all cases where we lint on the struct with this lint we're already lint on each individual field with "field is never read", right? So maybe we just extend the help text / messaging on the field-level lints to say that the struct is also not constructable, but omit the lint on the struct itself?

As another option, it seems plausible to me that we also want a lint on uncallable functions that reference such a struct -- maybe we can focus on that, something like unreachable_api?

View changes since the review

@mu001999 mu001999 Jun 6, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, I think in all cases where we lint on the struct with this lint we're already lint on each individual field with "field is never read", right? So maybe we just extend the help text / messaging on the field-level lints to say that the struct is also not constructable, but omit the lint on the struct itself?

Currently "field is never read" is a part of dead_code. This means we need to extend the scope of lint dead_code to include such structs, otherwise "field is never read" couldn't cover such structs. And I'm afraid this couldn't be done partly like only for field-level lints, which means we cannot achieve the goal, i.e., "extend the help text / messaging on the field-level lints".

I still prefer providing a separate lint, and keeping the scope of dead_code limited to non-exported items. Then users can allow or deny this lint separately.

As another option, it seems plausible to me that we also want a lint on uncallable functions that reference such a struct -- maybe we can focus on that, something like unreachable_api?

I like this idea! Especially since we could implement the extensions mentioned above in theory. Even with current scope of the lint, I think this name (or other variants) would still work, and would leave room for future extensions!

Deny,
"detects public structs with private fields that cannot be constructed or otherwise used through the external API"
}

declare_lint! {
/// The `unused_attributes` lint detects attributes that were not used by
/// the compiler.
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_middle/src/middle/dead_code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ pub struct DeadCodeLivenessSnapshot {
pub ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
}

/// Dead-code liveness data for both analysis phases.
/// Dead-code liveness data for different analysis phases.
///
/// `pre_deferred_seeding` is computed before reachable-public and `#[allow(dead_code)]` seeding,
/// and is used for lint `dead_code_pub_in_binary`.
/// `pre_unconstructable_pubs` is computed before reachable public structs that cannot be
/// directly constructed externally are seeded, and is used for lint
/// `unused_unconstructable_pub_structs`.
/// `final_result` is the final liveness snapshot used for lint `dead_code`.
#[derive(Clone, Debug, StableHash)]
pub struct DeadCodeLivenessSummary {
pub pre_deferred_seeding: DeadCodeLivenessSnapshot,
pub pre_unconstructable_pubs: DeadCodeLivenessSnapshot,
pub final_result: DeadCodeLivenessSnapshot,
}
163 changes: 146 additions & 17 deletions compiler/rustc_passes/src/dead.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
use rustc_abi::FieldIdx;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::{ErrorGuaranteed, MultiSpan};
use rustc_hir::def::{CtorOf, DefKind, Res};
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId, LocalModDefId};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{self as hir, ForeignItemId, ItemId, Node, PatKind, QPath, find_attr};
Expand All @@ -21,13 +21,15 @@ use rustc_middle::query::Providers;
use rustc_middle::ty::{self, AssocTag, TyCtxt};
use rustc_middle::{bug, span_bug};
use rustc_session::config::CrateType;
use rustc_session::lint::builtin::{DEAD_CODE, DEAD_CODE_PUB_IN_BINARY};
use rustc_session::lint::builtin::{
DEAD_CODE, DEAD_CODE_PUB_IN_BINARY, UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS,
};
use rustc_session::lint::{self, Lint, StableLintExpectationId};
use rustc_span::{Symbol, kw};

use crate::errors::{
ChangeFields, DeadCodePubInBinaryNote, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo,
UselessAssignment,
UnusedUnconstructablePubStructsNote, UselessAssignment,
};

/// Any local definition that may call something in its body block should be explored. For example,
Expand Down Expand Up @@ -70,6 +72,44 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
}
}

fn struct_can_be_constructed_directly(tcx: TyCtxt<'_>, id: LocalDefId) -> bool {
// Skip language items
if tcx.as_lang_item(id.to_def_id()).is_some() {
return true;
}

let adt_def = tcx.adt_def(id.to_def_id());

// We only care about structs for now
if !adt_def.is_struct() {
return true;
}

// Such types may be declared in Rust but constructed by FFI, so skip them
if adt_def.repr().c() || adt_def.repr().transparent() {
return true;
}

let typing_env = ty::TypingEnv::non_body_analysis(tcx, id);
let struct_ty = tcx.type_of(id).skip_binder();

// Skip if uninhabited, containing only ZST fields, or containing unit fields.
// Since such types are usually intentional type-level markers
if struct_ty.is_privately_uninhabited(tcx, typing_env)
|| tcx.layout_of(typing_env.as_query_input(struct_ty)).is_ok_and(|layout| layout.is_zst())
|| adt_def.all_fields().any(|field| tcx.type_of(field.did).skip_binder().is_unit())
{
return true;
}

adt_def.all_fields().all(|field| field.vis.is_public())
}

fn method_has_receiver(tcx: TyCtxt<'_>, id: LocalDefId) -> bool {
tcx.hir_fn_decl_by_hir_id(tcx.local_def_id_to_hir_id(id))
.is_some_and(|fn_decl| fn_decl.implicit_self().has_implicit_self())
}

/// Determine if a work from the worklist is coming from a `#[allow]`
/// or a `#[expect]` of `dead_code`
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
Expand Down Expand Up @@ -561,9 +601,12 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
(self.tcx.local_parent(local_def_id), trait_item_id)
}
// impl items are live if the corresponding traits are live
DefKind::Impl { of_trait: true } => {
(local_def_id, self.tcx.impl_trait_id(local_def_id).as_local())
}
DefKind::Impl { of_trait } => (
local_def_id,
of_trait
.then(|| self.tcx.impl_trait_id(local_def_id))
.and_then(|did| did.as_local()),
),
_ => bug!(),
};

Expand Down Expand Up @@ -921,12 +964,14 @@ fn maybe_record_as_seed<'tcx>(
struct SeedWorklists {
worklist: Vec<WorkItem>,
deferred_seeds: Vec<WorkItem>,
deferred_unconstructable_pubs: Vec<WorkItem>,
unsolved_items: Vec<LocalDefId>,
}

fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
let mut unsolved_items = Vec::new();
let mut deferred_seeds = Vec::new();
let mut deferred_unconstructable_pubs = Vec::new();
let mut worklist = Vec::new();

if let Some((def_id, _)) = tcx.entry_fn(())
Expand All @@ -940,12 +985,47 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
}

for (id, effective_vis) in tcx.effective_visibilities(()).iter() {
if effective_vis.is_public_at_level(Level::Reachable) {
deferred_seeds.push(WorkItem {
id: *id,
propagated: ComesFromAllowExpect::No,
own: ComesFromAllowExpect::No,
});
if !effective_vis.is_public_at_level(Level::Reachable) {
continue;
}

let work_item = WorkItem {
id: *id,
propagated: ComesFromAllowExpect::No,
own: ComesFromAllowExpect::No,
};

match tcx.def_kind(*id) {
DefKind::Struct if !struct_can_be_constructed_directly(tcx, *id) => {
deferred_unconstructable_pubs.push(work_item);
}
DefKind::Ctor(CtorOf::Struct, CtorKind::Fn)
if !struct_can_be_constructed_directly(tcx, tcx.local_parent(*id)) =>
{
deferred_unconstructable_pubs.push(work_item);
}

DefKind::Impl { of_trait } => {
if !of_trait {
unsolved_items.push(*id);
}

deferred_unconstructable_pubs.push(work_item);
}
DefKind::AssocFn
if let DefKind::Impl { of_trait } = tcx.def_kind(tcx.local_parent(*id))
&& method_has_receiver(tcx, *id) =>
{
if !of_trait {
unsolved_items.push(*id);
}

deferred_unconstructable_pubs.push(work_item);
}

_ => {
deferred_seeds.push(work_item);
}
}
}

Expand All @@ -958,15 +1038,19 @@ fn create_and_seed_worklist(tcx: TyCtxt<'_>) -> SeedWorklists {
maybe_record_as_seed(tcx, id, &mut push_into_worklist, &mut unsolved_items);
}

SeedWorklists { worklist, deferred_seeds, unsolved_items }
SeedWorklists { worklist, deferred_seeds, deferred_unconstructable_pubs, unsolved_items }
}

fn live_symbols_and_ignored_derived_traits(
tcx: TyCtxt<'_>,
(): (),
) -> Result<DeadCodeLivenessSummary, ErrorGuaranteed> {
let SeedWorklists { worklist, deferred_seeds, mut unsolved_items } =
create_and_seed_worklist(tcx);
let SeedWorklists {
worklist,
deferred_seeds,
deferred_unconstructable_pubs,
mut unsolved_items,
} = create_and_seed_worklist(tcx);
let mut symbol_visitor = MarkSymbolVisitor {
worklist,
tcx,
Expand All @@ -989,8 +1073,17 @@ fn live_symbols_and_ignored_derived_traits(
symbol_visitor.worklist.extend(deferred_seeds);
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;

let pre_unconstructable_pubs = DeadCodeLivenessSnapshot {
live_symbols: symbol_visitor.live_symbols.clone(),
ignored_derived_traits: symbol_visitor.ignored_derived_traits.clone(),
};

symbol_visitor.worklist.extend(deferred_unconstructable_pubs);
mark_live_symbols_and_ignored_derived_traits(&mut symbol_visitor, &mut unsolved_items)?;

Ok(DeadCodeLivenessSummary {
pre_deferred_seeding,
pre_unconstructable_pubs,
final_result: DeadCodeLivenessSnapshot {
live_symbols: symbol_visitor.live_symbols,
ignored_derived_traits: symbol_visitor.ignored_derived_traits,
Expand Down Expand Up @@ -1095,6 +1188,15 @@ impl<'tcx> DeadVisitor<'tcx> {
self.target_lint.name.eq(DEAD_CODE_PUB_IN_BINARY.name).then_some(DeadCodePubInBinaryNote)
}

fn unused_unconstructable_pub_structs_note(
&self,
) -> Option<UnusedUnconstructablePubStructsNote> {
self.target_lint
.name
.eq(UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS.name)
.then_some(UnusedUnconstructablePubStructsNote)
}

// # Panics
// All `dead_codes` must have the same lint level, otherwise we will intentionally ICE.
// This is because we emit a multi-spanned lint using the lint level of the `dead_codes`'s
Expand Down Expand Up @@ -1207,6 +1309,8 @@ impl<'tcx> DeadVisitor<'tcx> {
participle,
name_list,
dead_code_pub_in_binary_note: self.dead_code_pub_in_binary_note(),
unused_unconstructable_pub_structs_note: self
.unused_unconstructable_pub_structs_note(),
change_fields_suggestion: fields_suggestion,
parent_info,
ignored_derived_impls,
Expand Down Expand Up @@ -1244,6 +1348,8 @@ impl<'tcx> DeadVisitor<'tcx> {
participle,
name_list,
dead_code_pub_in_binary_note: self.dead_code_pub_in_binary_note(),
unused_unconstructable_pub_structs_note: self
.unused_unconstructable_pub_structs_note(),
parent_info,
ignored_derived_impls,
enum_variants_with_same_name,
Expand Down Expand Up @@ -1320,8 +1426,11 @@ impl<'tcx> DeadVisitor<'tcx> {
}

fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
let Ok(DeadCodeLivenessSummary { pre_deferred_seeding, final_result }) =
tcx.live_symbols_and_ignored_derived_traits(()).as_ref()
let Ok(DeadCodeLivenessSummary {
pre_deferred_seeding,
pre_unconstructable_pubs,
final_result,
}) = tcx.live_symbols_and_ignored_derived_traits(()).as_ref()
else {
return;
};
Expand All @@ -1347,6 +1456,26 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
);
}

let is_unused_unconstructable_pub = |def_id| {
tcx.effective_visibilities(()).is_public_at_level(def_id, Level::Reachable)
&& !pre_unconstructable_pubs.live_symbols.contains(&def_id)
&& tcx.def_kind(def_id) == DefKind::Struct
&& !struct_can_be_constructed_directly(tcx, def_id)
};
lint_dead_codes(
tcx,
UNUSED_UNCONSTRUCTABLE_PUB_STRUCTS,
module,
&pre_unconstructable_pubs.live_symbols,
&pre_unconstructable_pubs.ignored_derived_traits,
module_items
.free_items()
.filter(|free_item| is_unused_unconstructable_pub(free_item.owner_id.def_id)),
module_items
.foreign_items()
.filter(|foreign_item| is_unused_unconstructable_pub(foreign_item.owner_id.def_id)),
);

lint_dead_codes(
tcx,
DEAD_CODE,
Expand Down
12 changes: 12 additions & 0 deletions compiler/rustc_passes/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
#[subdiagnostic]
dead_code_pub_in_binary_note: Option<DeadCodePubInBinaryNote>,
#[subdiagnostic]
unused_unconstructable_pub_structs_note: Option<UnusedUnconstructablePubStructsNote>,
#[subdiagnostic]
// only on DeadCodes since it's never a problem for tuple struct fields
enum_variants_with_same_name: Vec<EnumVariantSameName<'tcx>>,
#[subdiagnostic]
Expand All @@ -949,6 +951,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
#[subdiagnostic]
dead_code_pub_in_binary_note: Option<DeadCodePubInBinaryNote>,
#[subdiagnostic]
unused_unconstructable_pub_structs_note: Option<UnusedUnconstructablePubStructsNote>,
#[subdiagnostic]
change_fields_suggestion: ChangeFields,
#[subdiagnostic]
parent_info: Option<ParentInfo<'tcx>>,
Expand All @@ -963,6 +967,14 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
)]
pub(crate) struct DeadCodePubInBinaryNote;

#[derive(Subdiagnostic)]
#[note(
"this `pub` struct has private fields, no public constructor, and is not otherwise reachable through the external API"
)]
#[help("consider removing it if it has not been used")]
#[help("consider adding a field of type `!` or `()` if it is intended")]
pub(crate) struct UnusedUnconstructablePubStructsNote;

#[derive(Subdiagnostic)]
#[note(
"it is impossible to refer to the {$dead_descr} `{$dead_name}` because it is shadowed by this enum variant with the same name"
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ pub struct FromUtf8Error {
/// assert!(String::from_utf16(v).is_err());
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(no_global_oom_handling, expect(unused_unconstructable_pub_structs))]
#[derive(Debug)]
pub struct FromUtf16Error {
kind: FromUtf16ErrorKind,
Expand Down
Loading
Loading