Skip to content

Commit a4f8fce

Browse files
committed
Implement unused_pub_items_in_binary lint
1 parent 97ca81c commit a4f8fce

23 files changed

Lines changed: 330 additions & 7 deletions

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ declare_lint_pass! {
148148
UNUSED_MACROS,
149149
UNUSED_MACRO_RULES,
150150
UNUSED_MUT,
151+
UNUSED_PUB_ITEMS_IN_BINARY,
151152
UNUSED_QUALIFICATIONS,
152153
UNUSED_UNSAFE,
153154
UNUSED_VARIABLES,
@@ -784,6 +785,37 @@ declare_lint! {
784785
"detect unused, unexported items"
785786
}
786787

788+
declare_lint! {
789+
/// The `unused_pub_items_in_binary` lint detects unused `pub` items in
790+
/// executable crates.
791+
///
792+
/// ### Example
793+
///
794+
/// ```rust
795+
/// #![deny(unused_pub_items_in_binary)]
796+
///
797+
/// pub fn unused_pub_fn() {}
798+
///
799+
/// fn main() {}
800+
/// ```
801+
///
802+
/// {{produces}}
803+
///
804+
/// ### Explanation
805+
///
806+
/// In executable crates, `pub` items are often implementation details
807+
/// rather than part of an external API. This lint helps find those items
808+
/// when they are never used.
809+
///
810+
/// This lint only applies to executable crates. In library crates, public
811+
/// items are considered part of the crate's API and are not reported by
812+
/// this lint.
813+
pub UNUSED_PUB_ITEMS_IN_BINARY,
814+
Allow,
815+
"detect public items in executable crates that are never used",
816+
crate_level_only
817+
}
818+
787819
declare_lint! {
788820
/// The `unused_attributes` lint detects attributes that were not used by
789821
/// the compiler.

compiler/rustc_middle/src/middle/dead_code.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ pub struct DeadCodeLivenessSnapshot {
1111
pub ignored_derived_traits: LocalDefIdMap<FxIndexSet<DefId>>,
1212
}
1313

14-
/// Dead-code liveness data across primary and deferred seeding.
14+
/// Dead-code liveness data for both analysis phases.
1515
///
16-
/// `pre_deferred_seeding` is computed after the initial analysis pass but before deferred seeds
17-
/// are added.
18-
/// `final_result` is the liveness snapshot after all seeds have been processed.
16+
/// `pre_deferred_seeding` is computed before reachable-public and `#[allow(dead_code)]` seeding,
17+
/// and is used for lint `unused_pub_items_in_binary`.
18+
/// `final_result` is the final liveness snapshot used for lint `dead_code`.
1919
#[derive(Clone, Debug, HashStable)]
2020
pub struct DeadCodeLivenessSummary {
2121
pub pre_deferred_seeding: DeadCodeLivenessSnapshot,

compiler/rustc_passes/src/check_attr.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,16 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
15261526
return;
15271527
}
15281528
}
1529+
} else if hir_id == CRATE_HIR_ID
1530+
&& attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect])
1531+
&& let Some(meta) = attr.meta_item_list()
1532+
&& meta.iter().any(|meta| {
1533+
meta.meta_item()
1534+
.is_some_and(|item| item.path == sym::unused_pub_items_in_binary)
1535+
})
1536+
&& !self.tcx.crate_types().contains(&CrateType::Executable)
1537+
{
1538+
errors::UnusedNote::NoEffectUnusedPubItemsInBinary
15291539
} else if attr.has_name(sym::default_method_body_is_const) {
15301540
errors::UnusedNote::DefaultMethodBodyConst
15311541
} else {

compiler/rustc_passes/src/dead.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ use rustc_middle::middle::privacy::Level;
2020
use rustc_middle::query::Providers;
2121
use rustc_middle::ty::{self, AssocTag, TyCtxt};
2222
use rustc_middle::{bug, span_bug};
23-
use rustc_session::lint::builtin::DEAD_CODE;
23+
use rustc_session::config::CrateType;
24+
use rustc_session::lint::builtin::{DEAD_CODE, UNUSED_PUB_ITEMS_IN_BINARY};
2425
use rustc_session::lint::{self, Lint, LintExpectationId};
2526
use rustc_span::{Symbol, kw};
2627

2728
use crate::errors::{
28-
ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment,
29+
ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UnusedPubItemsInBinaryNote,
30+
UselessAssignment,
2931
};
3032

3133
/// Any local definition that may call something in its body block should be explored. For example,
@@ -1086,6 +1088,13 @@ impl<'tcx> DeadVisitor<'tcx> {
10861088
(level.level, level.lint_id)
10871089
}
10881090

1091+
fn unused_pub_items_in_binary_note(&self) -> Option<UnusedPubItemsInBinaryNote> {
1092+
self.target_lint
1093+
.name
1094+
.eq(UNUSED_PUB_ITEMS_IN_BINARY.name)
1095+
.then_some(UnusedPubItemsInBinaryNote)
1096+
}
1097+
10891098
// # Panics
10901099
// All `dead_codes` must have the same lint level, otherwise we will intentionally ICE.
10911100
// This is because we emit a multi-spanned lint using the lint level of the `dead_codes`'s
@@ -1197,6 +1206,7 @@ impl<'tcx> DeadVisitor<'tcx> {
11971206
descr,
11981207
participle,
11991208
name_list,
1209+
unused_pub_items_in_binary_note: self.unused_pub_items_in_binary_note(),
12001210
change_fields_suggestion: fields_suggestion,
12011211
parent_info,
12021212
ignored_derived_impls,
@@ -1233,6 +1243,7 @@ impl<'tcx> DeadVisitor<'tcx> {
12331243
descr,
12341244
participle,
12351245
name_list,
1246+
unused_pub_items_in_binary_note: self.unused_pub_items_in_binary_note(),
12361247
parent_info,
12371248
ignored_derived_impls,
12381249
enum_variants_with_same_name,
@@ -1308,14 +1319,33 @@ impl<'tcx> DeadVisitor<'tcx> {
13081319
}
13091320

13101321
fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1311-
let Ok(DeadCodeLivenessSummary { final_result, .. }) =
1322+
let Ok(DeadCodeLivenessSummary { pre_deferred_seeding, final_result }) =
13121323
tcx.live_symbols_and_ignored_derived_traits(()).as_ref()
13131324
else {
13141325
return;
13151326
};
13161327

13171328
let module_items = tcx.hir_module_items(module);
13181329

1330+
if tcx.crate_types().contains(&CrateType::Executable) {
1331+
let is_unused_pub = |def_id: LocalDefId| {
1332+
tcx.effective_visibilities(()).is_public_at_level(def_id, Level::Reachable)
1333+
&& !pre_deferred_seeding.live_symbols.contains(&def_id)
1334+
};
1335+
1336+
lint_dead_code_or_unused_pub_items_in_binary(
1337+
tcx,
1338+
UNUSED_PUB_ITEMS_IN_BINARY,
1339+
module,
1340+
&pre_deferred_seeding.live_symbols,
1341+
&pre_deferred_seeding.ignored_derived_traits,
1342+
module_items.free_items().filter(|free_item| is_unused_pub(free_item.owner_id.def_id)),
1343+
module_items
1344+
.foreign_items()
1345+
.filter(|foreign_item| is_unused_pub(foreign_item.owner_id.def_id)),
1346+
);
1347+
}
1348+
13191349
lint_dead_code_or_unused_pub_items_in_binary(
13201350
tcx,
13211351
DEAD_CODE,

compiler/rustc_passes/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ pub(crate) enum UnusedNote {
298298
"the `linker_messages` and `linker_info` lints can only be controlled at the root of a crate that needs to be linked"
299299
)]
300300
LinkerMessagesBinaryCrateOnly,
301+
#[note("the `unused_pub_items_in_binary` lint has no effect in library crates")]
302+
NoEffectUnusedPubItemsInBinary,
301303
}
302304

303305
#[derive(Diagnostic)]
@@ -920,6 +922,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
920922
participle: &'tcx str,
921923
name_list: DiagSymbolList,
922924
#[subdiagnostic]
925+
unused_pub_items_in_binary_note: Option<UnusedPubItemsInBinaryNote>,
926+
#[subdiagnostic]
923927
// only on DeadCodes since it's never a problem for tuple struct fields
924928
enum_variants_with_same_name: Vec<EnumVariantSameName<'tcx>>,
925929
#[subdiagnostic]
@@ -943,6 +947,8 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
943947
participle: &'tcx str,
944948
name_list: DiagSymbolList,
945949
#[subdiagnostic]
950+
unused_pub_items_in_binary_note: Option<UnusedPubItemsInBinaryNote>,
951+
#[subdiagnostic]
946952
change_fields_suggestion: ChangeFields,
947953
#[subdiagnostic]
948954
parent_info: Option<ParentInfo<'tcx>>,
@@ -951,6 +957,12 @@ pub(crate) enum MultipleDeadCodes<'tcx> {
951957
},
952958
}
953959

960+
#[derive(Subdiagnostic)]
961+
#[note(
962+
"in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused"
963+
)]
964+
pub(crate) struct UnusedPubItemsInBinaryNote;
965+
954966
#[derive(Subdiagnostic)]
955967
#[note(
956968
"it is impossible to refer to the {$dead_descr} `{$dead_name}` because it is shadowed by this enum variant with the same name"

compiler/rustc_span/src/symbol.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2189,6 +2189,7 @@ symbols! {
21892189
unstable_removed,
21902190
untagged_unions,
21912191
unused_imports,
2192+
unused_pub_items_in_binary,
21922193
unwind,
21932194
unwind_attributes,
21942195
unwind_safe_trait,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#![deny(dead_code)]
2+
#![deny(unused_pub_items_in_binary)]
3+
4+
pub fn g() {} //~ ERROR function `g` is never used
5+
6+
fn h() {}
7+
8+
#[allow(dead_code)]
9+
fn f() {
10+
g();
11+
h();
12+
}
13+
14+
fn main() {}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: function `g` is never used
2+
--> $DIR/allow-dead-code-transitive.rs:4:8
3+
|
4+
LL | pub fn g() {}
5+
| ^
6+
|
7+
= note: in libraries, `pub` items can be used by dependent crates; in binaries, they cannot, so this `pub` item is unused
8+
note: the lint level is defined here
9+
--> $DIR/allow-dead-code-transitive.rs:2:9
10+
|
11+
LL | #![deny(unused_pub_items_in_binary)]
12+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#![deny(dead_code)]
2+
#![allow(unused_pub_items_in_binary)]
3+
4+
pub fn unused_pub_fn() {
5+
helper();
6+
}
7+
8+
fn helper() {}
9+
10+
fn unused_priv_fn() {} //~ ERROR function `unused_priv_fn` is never used
11+
12+
fn main() {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error: function `unused_priv_fn` is never used
2+
--> $DIR/allow-lint.rs:10:4
3+
|
4+
LL | fn unused_priv_fn() {}
5+
| ^^^^^^^^^^^^^^
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/allow-lint.rs:1:9
9+
|
10+
LL | #![deny(dead_code)]
11+
| ^^^^^^^^^
12+
13+
error: aborting due to 1 previous error
14+

0 commit comments

Comments
 (0)