@@ -9,7 +9,7 @@ use hir::def_id::{LocalDefIdMap, LocalDefIdSet};
9
9
use rustc_abi:: FieldIdx ;
10
10
use rustc_data_structures:: fx:: FxIndexSet ;
11
11
use rustc_errors:: MultiSpan ;
12
- use rustc_hir:: def:: { CtorOf , DefKind , Res } ;
12
+ use rustc_hir:: def:: { CtorKind , CtorOf , DefKind , Res } ;
13
13
use rustc_hir:: def_id:: { DefId , LocalDefId , LocalModDefId } ;
14
14
use rustc_hir:: intravisit:: { self , Visitor } ;
15
15
use rustc_hir:: { self as hir, Node , PatKind , QPath } ;
@@ -18,12 +18,13 @@ use rustc_middle::middle::privacy::Level;
18
18
use rustc_middle:: query:: Providers ;
19
19
use rustc_middle:: ty:: { self , AssocTag , TyCtxt } ;
20
20
use rustc_middle:: { bug, span_bug} ;
21
- use rustc_session:: lint:: builtin:: DEAD_CODE ;
21
+ use rustc_session:: lint:: builtin:: { DEAD_CODE , UNCONSTRUCTIBLE_PUB_STRUCT } ;
22
22
use rustc_session:: lint:: { self , LintExpectationId } ;
23
23
use rustc_span:: { Symbol , kw, sym} ;
24
24
25
25
use crate :: errors:: {
26
- ChangeFields , IgnoredDerivedImpls , MultipleDeadCodes , ParentInfo , UselessAssignment ,
26
+ ChangeFields , IgnoredDerivedImpls , MultipleDeadCodes , ParentInfo , UnconstructiblePubStruct ,
27
+ UselessAssignment ,
27
28
} ;
28
29
29
30
/// Any local definition that may call something in its body block should be explored. For example,
@@ -66,6 +67,38 @@ fn should_explore(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
66
67
}
67
68
}
68
69
70
+ fn struct_can_be_constructed_directly ( tcx : TyCtxt < ' _ > , id : LocalDefId ) -> bool {
71
+ let adt_def = tcx. adt_def ( id) ;
72
+
73
+ // Skip types contain fields of unit and never type,
74
+ // it's usually intentional to make the type not constructible
75
+ if adt_def. all_fields ( ) . any ( |field| {
76
+ let field_type = tcx. type_of ( field. did ) . instantiate_identity ( ) ;
77
+ field_type. is_unit ( ) || field_type. is_never ( )
78
+ } ) {
79
+ return true ;
80
+ }
81
+
82
+ return adt_def. all_fields ( ) . all ( |field| {
83
+ let field_type = tcx. type_of ( field. did ) . instantiate_identity ( ) ;
84
+ // Skip fields of PhantomData,
85
+ // cause it's a common way to check things like well-formedness
86
+ if field_type. is_phantom_data ( ) {
87
+ return true ;
88
+ }
89
+
90
+ field. vis . is_public ( )
91
+ } ) ;
92
+ }
93
+
94
+ fn method_has_no_receiver ( tcx : TyCtxt < ' _ > , id : LocalDefId ) -> bool {
95
+ if let Some ( fn_decl) = tcx. hir_fn_decl_by_hir_id ( tcx. local_def_id_to_hir_id ( id) ) {
96
+ !fn_decl. implicit_self . has_implicit_self ( )
97
+ } else {
98
+ true
99
+ }
100
+ }
101
+
69
102
/// Determine if a work from the worklist is coming from a `#[allow]`
70
103
/// or a `#[expect]` of `dead_code`
71
104
#[ derive( Debug , Copy , Clone , Eq , PartialEq , Hash ) ]
@@ -370,6 +403,28 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
370
403
}
371
404
}
372
405
406
+ fn mark_live_symbols_until_unsolved_items_fixed (
407
+ & mut self ,
408
+ unsolved_items : & mut Vec < LocalDefId > ,
409
+ ) {
410
+ self . mark_live_symbols ( ) ;
411
+
412
+ // We have marked the primary seeds as live. We now need to process unsolved items from traits
413
+ // and trait impls: add them to the work list if the trait or the implemented type is live.
414
+ let mut items_to_check: Vec < _ > = unsolved_items
415
+ . extract_if ( .., |& mut local_def_id| self . check_impl_or_impl_item_live ( local_def_id) )
416
+ . collect ( ) ;
417
+
418
+ while !items_to_check. is_empty ( ) {
419
+ self . worklist . extend ( items_to_check. drain ( ..) . map ( |id| ( id, ComesFromAllowExpect :: No ) ) ) ;
420
+ self . mark_live_symbols ( ) ;
421
+
422
+ items_to_check. extend ( unsolved_items. extract_if ( .., |& mut local_def_id| {
423
+ self . check_impl_or_impl_item_live ( local_def_id)
424
+ } ) ) ;
425
+ }
426
+ }
427
+
373
428
/// Automatically generated items marked with `rustc_trivial_field_reads`
374
429
/// will be ignored for the purposes of dead code analysis (see PR #85200
375
430
/// for discussion).
@@ -492,7 +547,7 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
492
547
. and_then ( |def_id| def_id. as_local ( ) ) ,
493
548
) ,
494
549
// impl items are live if the corresponding traits are live
495
- DefKind :: Impl { of_trait : true } => (
550
+ DefKind :: Impl { .. } => (
496
551
local_def_id,
497
552
self . tcx
498
553
. impl_trait_ref ( local_def_id)
@@ -684,6 +739,12 @@ impl<'tcx> Visitor<'tcx> for MarkSymbolVisitor<'tcx> {
684
739
}
685
740
}
686
741
742
+ fn has_allow_unconstructible_pub_struct ( tcx : TyCtxt < ' _ > , def_id : LocalDefId ) -> bool {
743
+ let hir_id = tcx. local_def_id_to_hir_id ( def_id) ;
744
+ let lint_level = tcx. lint_level_at_node ( UNCONSTRUCTIBLE_PUB_STRUCT , hir_id) . level ;
745
+ matches ! ( lint_level, lint:: Allow | lint:: Expect )
746
+ }
747
+
687
748
fn has_allow_dead_code_or_lang_attr (
688
749
tcx : TyCtxt < ' _ > ,
689
750
def_id : LocalDefId ,
@@ -743,7 +804,9 @@ fn maybe_record_as_seed<'tcx>(
743
804
unsolved_items : & mut Vec < LocalDefId > ,
744
805
) {
745
806
let allow_dead_code = has_allow_dead_code_or_lang_attr ( tcx, owner_id. def_id ) ;
746
- if let Some ( comes_from_allow) = allow_dead_code {
807
+ if let Some ( comes_from_allow) = allow_dead_code
808
+ && !tcx. effective_visibilities ( ( ) ) . is_reachable ( owner_id. def_id )
809
+ {
747
810
worklist. push ( ( owner_id. def_id , comes_from_allow) ) ;
748
811
}
749
812
@@ -807,6 +870,33 @@ fn create_and_seed_worklist(
807
870
effective_vis
808
871
. is_public_at_level ( Level :: Reachable )
809
872
. then_some ( id)
873
+ . filter ( |id| {
874
+ let ( is_seed, is_impl_or_impl_item) = match tcx. def_kind ( * id) {
875
+ DefKind :: Impl { .. } => ( false , true ) ,
876
+ DefKind :: AssocFn => (
877
+ !matches ! ( tcx. def_kind( tcx. local_parent( * id) ) , DefKind :: Impl { .. } )
878
+ || method_has_no_receiver ( tcx, * id) ,
879
+ true ,
880
+ ) ,
881
+ DefKind :: Struct => (
882
+ has_allow_unconstructible_pub_struct ( tcx, * id)
883
+ || struct_can_be_constructed_directly ( tcx, * id) ,
884
+ false ,
885
+ ) ,
886
+ DefKind :: Ctor ( CtorOf :: Struct , CtorKind :: Fn ) => (
887
+ has_allow_unconstructible_pub_struct ( tcx, tcx. local_parent ( * id) )
888
+ || struct_can_be_constructed_directly ( tcx, tcx. local_parent ( * id) ) ,
889
+ false ,
890
+ ) ,
891
+ _ => ( true , false ) ,
892
+ } ;
893
+
894
+ if !is_seed && is_impl_or_impl_item {
895
+ unsolved_impl_item. push ( * id) ;
896
+ }
897
+
898
+ is_seed
899
+ } )
810
900
. map ( |id| ( id, ComesFromAllowExpect :: No ) )
811
901
} )
812
902
// Seed entry point
@@ -827,7 +917,7 @@ fn create_and_seed_worklist(
827
917
fn live_symbols_and_ignored_derived_traits (
828
918
tcx : TyCtxt < ' _ > ,
829
919
( ) : ( ) ,
830
- ) -> ( LocalDefIdSet , LocalDefIdMap < FxIndexSet < DefId > > ) {
920
+ ) -> ( LocalDefIdSet , LocalDefIdMap < FxIndexSet < DefId > > , LocalDefIdSet ) {
831
921
let ( worklist, mut unsolved_items) = create_and_seed_worklist ( tcx) ;
832
922
let mut symbol_visitor = MarkSymbolVisitor {
833
923
worklist,
@@ -841,28 +931,29 @@ fn live_symbols_and_ignored_derived_traits(
841
931
ignore_variant_stack : vec ! [ ] ,
842
932
ignored_derived_traits : Default :: default ( ) ,
843
933
} ;
844
- symbol_visitor. mark_live_symbols ( ) ;
934
+ symbol_visitor. mark_live_symbols_until_unsolved_items_fixed ( & mut unsolved_items ) ;
845
935
846
- // We have marked the primary seeds as live. We now need to process unsolved items from traits
847
- // and trait impls: add them to the work list if the trait or the implemented type is live.
848
- let mut items_to_check: Vec < _ > = unsolved_items
849
- . extract_if ( .., |& mut local_def_id| {
850
- symbol_visitor. check_impl_or_impl_item_live ( local_def_id)
851
- } )
852
- . collect ( ) ;
936
+ let reachable_items =
937
+ tcx. effective_visibilities ( ( ) ) . iter ( ) . filter_map ( |( & id, effective_vis) | {
938
+ effective_vis. is_public_at_level ( Level :: Reachable ) . then_some ( id)
939
+ } ) ;
853
940
854
- while !items_to_check. is_empty ( ) {
855
- symbol_visitor
856
- . worklist
857
- . extend ( items_to_check. drain ( ..) . map ( |id| ( id, ComesFromAllowExpect :: No ) ) ) ;
858
- symbol_visitor. mark_live_symbols ( ) ;
941
+ let mut unstructurable_pub_structs = LocalDefIdSet :: default ( ) ;
942
+ for id in reachable_items {
943
+ if symbol_visitor. live_symbols . contains ( & id) {
944
+ continue ;
945
+ }
946
+
947
+ if matches ! ( tcx. def_kind( id) , DefKind :: Struct ) {
948
+ unstructurable_pub_structs. insert ( id) ;
949
+ }
859
950
860
- items_to_check. extend ( unsolved_items. extract_if ( .., |& mut local_def_id| {
861
- symbol_visitor. check_impl_or_impl_item_live ( local_def_id)
862
- } ) ) ;
951
+ symbol_visitor. worklist . push ( ( id, ComesFromAllowExpect :: No ) ) ;
863
952
}
864
953
865
- ( symbol_visitor. live_symbols , symbol_visitor. ignored_derived_traits )
954
+ symbol_visitor. mark_live_symbols_until_unsolved_items_fixed ( & mut unsolved_items) ;
955
+
956
+ ( symbol_visitor. live_symbols , symbol_visitor. ignored_derived_traits , unstructurable_pub_structs)
866
957
}
867
958
868
959
struct DeadItem {
@@ -1142,7 +1233,8 @@ impl<'tcx> DeadVisitor<'tcx> {
1142
1233
}
1143
1234
1144
1235
fn check_mod_deathness ( tcx : TyCtxt < ' _ > , module : LocalModDefId ) {
1145
- let ( live_symbols, ignored_derived_traits) = tcx. live_symbols_and_ignored_derived_traits ( ( ) ) ;
1236
+ let ( live_symbols, ignored_derived_traits, unstructurable_pub_structs) =
1237
+ tcx. live_symbols_and_ignored_derived_traits ( ( ) ) ;
1146
1238
let mut visitor = DeadVisitor { tcx, live_symbols, ignored_derived_traits } ;
1147
1239
1148
1240
let module_items = tcx. hir_module_items ( module) ;
@@ -1229,6 +1321,27 @@ fn check_mod_deathness(tcx: TyCtxt<'_>, module: LocalModDefId) {
1229
1321
for foreign_item in module_items. foreign_items ( ) {
1230
1322
visitor. check_definition ( foreign_item. owner_id . def_id ) ;
1231
1323
}
1324
+
1325
+ for item in module_items. free_items ( ) {
1326
+ let def_id = item. owner_id . def_id ;
1327
+
1328
+ if !unstructurable_pub_structs. contains ( & def_id) {
1329
+ continue ;
1330
+ }
1331
+
1332
+ let Some ( name) = tcx. opt_item_name ( def_id. to_def_id ( ) ) else {
1333
+ continue ;
1334
+ } ;
1335
+
1336
+ if name. as_str ( ) . starts_with ( '_' ) {
1337
+ continue ;
1338
+ }
1339
+
1340
+ let hir_id = tcx. local_def_id_to_hir_id ( def_id) ;
1341
+ let vis_span = tcx. hir_node ( hir_id) . expect_item ( ) . vis_span ;
1342
+ let diag = UnconstructiblePubStruct { name, vis_span } ;
1343
+ tcx. emit_node_span_lint ( UNCONSTRUCTIBLE_PUB_STRUCT , hir_id, tcx. hir_span ( hir_id) , diag) ;
1344
+ }
1232
1345
}
1233
1346
1234
1347
pub ( crate ) fn provide ( providers : & mut Providers ) {
0 commit comments