Skip to content

Commit ef7850c

Browse files
committed
generalize re-export stability checks
Signed-off-by: Amirhossein Akhlaghpour <m9.akhlaghpoor@gmail.com>
1 parent fbec8a6 commit ef7850c

67 files changed

Lines changed: 634 additions & 183 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub mod hardwired {
5454
FUNCTION_ITEM_REFERENCES,
5555
HIDDEN_GLOB_REEXPORTS,
5656
ILL_FORMED_ATTRIBUTE_INPUT,
57+
INCOMPATIBLE_REEXPORT_STABILITY,
5758
INCOMPLETE_INCLUDE,
5859
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
5960
INLINE_NO_SANITIZE,
@@ -152,7 +153,6 @@ pub mod hardwired {
152153
UNUSED_MUT,
153154
UNUSED_QUALIFICATIONS,
154155
UNUSED_UNSAFE,
155-
UNUSED_UNSTABLE_REEXPORT_ATTRIBUTES,
156156
UNUSED_VARIABLES,
157157
UNUSED_VISIBILITIES,
158158
USELESS_DEPRECATED,
@@ -2793,20 +2793,21 @@ declare_lint! {
27932793
}
27942794

27952795
declare_lint! {
2796-
/// The `unused_unstable_reexport_attributes` lint detects `#[unstable]` attributes
2797-
/// on re-exports where the attribute does not make the re-exported path unstable.
2796+
/// The `incompatible_reexport_stability` lint detects stability
2797+
/// annotations on re-exports that are incompatible with the stability
2798+
/// metadata of the re-exported item.
27982799
///
27992800
/// ### Example
28002801
///
28012802
/// ```rust,compile_fail
28022803
/// #![feature(staged_api)]
28032804
/// #![stable(feature = "test", since = "1.0.0")]
28042805
///
2805-
/// #[stable(feature = "test", since = "1.0.0")]
2806+
/// #[stable(feature = "original", since = "1.0.0")]
28062807
/// pub struct S;
28072808
///
2808-
/// #[unstable(feature = "reexport", issue = "none")]
2809-
/// pub use crate::S as T;
2809+
/// #[stable(feature = "different", since = "1.0.0")]
2810+
/// pub use self::S as T;
28102811
///
28112812
/// fn main() {}
28122813
/// ```
@@ -2815,11 +2816,14 @@ declare_lint! {
28152816
///
28162817
/// ### Explanation
28172818
///
2818-
/// Stability attributes on re-exports do not currently change the
2819-
/// stability of an otherwise stable re-exported item.
2820-
pub UNUSED_UNSTABLE_REEXPORT_ATTRIBUTES,
2819+
/// Stability annotations on re-exports should be compatible with the
2820+
/// stability metadata of the item being re-exported. Stable metadata is
2821+
/// compared by feature and `since`, while unstable metadata is compared by
2822+
/// feature and issue. Stable re-exports of unstable definitions remain
2823+
/// handled by the existing stability machinery.
2824+
pub INCOMPATIBLE_REEXPORT_STABILITY,
28212825
Deny,
2822-
"detects ineffective `#[unstable]` attributes on re-exports",
2826+
"detects incompatible stability annotations on re-exports",
28232827
@feature_gate = staged_api;
28242828
}
28252829

compiler/rustc_passes/src/diagnostics.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -965,8 +965,8 @@ pub(crate) struct UnnecessaryPartialStableFeature {
965965
pub(crate) struct IneffectiveUnstableImpl;
966966

967967
#[derive(Diagnostic)]
968-
#[diag("`#[unstable]` does not make this re-exported path unstable")]
969-
pub(crate) struct UnusedUnstableReexportAttributes;
968+
#[diag("stability annotation on this re-export does not match the re-exported item")]
969+
pub(crate) struct IncompatibleReexportStability;
970970

971971
// FIXME(jdonszelmann): move back to rustc_attr
972972
#[derive(Diagnostic)]

compiler/rustc_passes/src/stability.rs

Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use rustc_hir::{
1818
};
1919
use rustc_lint_defs as lint;
2020
use rustc_lint_defs::builtin::{
21-
DEPRECATED, DUPLICATE_FEATURES, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, STABLE_FEATURES,
22-
UNUSED_UNSTABLE_REEXPORT_ATTRIBUTES,
21+
DEPRECATED, DUPLICATE_FEATURES, INCOMPATIBLE_REEXPORT_STABILITY,
22+
INEFFECTIVE_UNSTABLE_TRAIT_IMPL, STABLE_FEATURES,
2323
};
2424
use rustc_middle::hir::nested_filter;
2525
use rustc_middle::metadata::Reexport;
@@ -525,9 +525,9 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
525525
/// Cross-references the feature names of unstable APIs with enabled
526526
/// features and possibly prints errors.
527527
fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, mod_id: LocalModId) {
528-
let mut checker = Checker { tcx, mod_id, unstable_reexports: FxIndexMap::default() };
528+
let mut checker = Checker { tcx, mod_id, reexport_stability: FxIndexMap::default() };
529529
tcx.hir_visit_item_likes_in_module(mod_id, &mut checker);
530-
checker.emit_unused_unstable_reexport_attributes();
530+
checker.emit_incompatible_reexport_stability();
531531

532532
let is_staged_api =
533533
tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api();
@@ -557,99 +557,140 @@ pub(crate) fn provide(providers: &mut Providers) {
557557
};
558558
}
559559

560-
struct UnstableReexport {
560+
struct ReexportStability {
561561
hir_id: HirId,
562562
span: Span,
563563
has_target: bool,
564-
all_targets_stable: bool,
564+
all_targets_compatible: bool,
565565
}
566566

567567
struct Checker<'tcx> {
568568
tcx: TyCtxt<'tcx>,
569569
mod_id: LocalModId,
570-
unstable_reexports: FxIndexMap<Span, UnstableReexport>,
570+
reexport_stability: FxIndexMap<Span, ReexportStability>,
571571
}
572572

573573
impl<'tcx> Checker<'tcx> {
574-
fn unstable_reexport_span(&self, item: &'tcx hir::Item<'tcx>) -> Option<Span> {
574+
fn reexport_stability_attr(&self, item: &'tcx hir::Item<'tcx>) -> Option<(Stability, Span)> {
575575
let attrs = self.tcx.hir_attrs(item.hir_id());
576-
let (stability, span) =
577-
find_attr!(attrs, Stability { stability, span } => (*stability, *span))?;
576+
find_attr!(attrs, Stability { stability, span } => (*stability, *span))
577+
}
578+
579+
fn stability_is_compatible(reexport: &Stability, target: &Stability) -> bool {
580+
match (&reexport.level, &target.level) {
581+
(
582+
StabilityLevel::Stable { since: reexport_since, .. },
583+
StabilityLevel::Stable { since: target_since, .. },
584+
) => {
585+
// Avoid another error for an invalid `since`.
586+
matches!(
587+
(*reexport_since, *target_since),
588+
(StableSince::Err(_), _) | (_, StableSince::Err(_))
589+
) || (reexport.feature == target.feature && reexport_since == target_since)
590+
}
591+
592+
(
593+
StabilityLevel::Unstable { issue: reexport_issue, .. },
594+
StabilityLevel::Unstable { issue: target_issue, .. },
595+
) => reexport.feature == target.feature && reexport_issue == target_issue,
578596

579-
matches!(stability.level, StabilityLevel::Unstable { .. }).then_some(span)
597+
// An unstable re-export cannot make a stable item unstable.
598+
(StabilityLevel::Unstable { .. }, StabilityLevel::Stable { .. }) => false,
599+
600+
// Stable re-exports of unstable items are handled elsewhere.
601+
(StabilityLevel::Stable { .. }, StabilityLevel::Unstable { .. }) => true,
602+
}
580603
}
581604

582605
fn classify_reexport_targets<Id>(
583606
&self,
607+
own_stability: &Stability,
584608
targets: impl IntoIterator<Item = Res<Id>>,
585609
) -> (bool, bool) {
586610
let mut has_target = false;
587-
let mut all_targets_stable = true;
611+
let mut all_targets_compatible = true;
588612

589613
for res in targets {
590614
match res {
591615
Res::Def(_, def_id) => {
592616
has_target = true;
593617

594-
if self.tcx.lookup_stability(def_id).is_some_and(|stab| !stab.level.is_stable())
618+
if let Some(target_stability) = self.tcx.lookup_stability(def_id)
619+
&& !Self::stability_is_compatible(own_stability, &target_stability)
595620
{
596-
all_targets_stable = false;
621+
all_targets_compatible = false;
597622
}
598623
}
599624

600625
Res::PrimTy(_) => {
601626
has_target = true;
602-
}
603627

604-
_ => {
605-
all_targets_stable = false;
628+
// Primitives are stable and have no DefId.
629+
if own_stability.level.is_unstable() {
630+
all_targets_compatible = false;
631+
}
606632
}
633+
634+
// No stability metadata to compare.
635+
_ => {}
607636
}
608637
}
609638

610-
(has_target, all_targets_stable)
639+
(has_target, all_targets_compatible)
611640
}
612641

613-
fn record_unstable_reexport(
642+
fn record_reexport_stability(
614643
&mut self,
615644
item: &'tcx hir::Item<'tcx>,
616645
attr_span: Span,
617646
span: Span,
618647
has_target: bool,
619-
all_targets_stable: bool,
648+
all_targets_compatible: bool,
620649
) {
621-
let entry = self.unstable_reexports.entry(attr_span).or_insert(UnstableReexport {
650+
let entry = self.reexport_stability.entry(attr_span).or_insert(ReexportStability {
622651
hir_id: item.hir_id(),
623652
span,
624653
has_target: false,
625-
all_targets_stable: true,
654+
all_targets_compatible: true,
626655
});
627656

628657
entry.has_target |= has_target;
629-
entry.all_targets_stable &= all_targets_stable;
658+
659+
// Keep the first bad path for the diagnostic.
660+
if entry.all_targets_compatible && !all_targets_compatible {
661+
entry.span = span;
662+
}
663+
664+
entry.all_targets_compatible &= all_targets_compatible;
630665
}
631666

632-
fn check_single_unstable_reexport(
667+
fn check_single_reexport_stability(
633668
&mut self,
634669
item: &'tcx hir::Item<'tcx>,
635670
path: &'tcx UsePath<'tcx>,
636671
) {
637-
let Some(attr_span) = self.unstable_reexport_span(item) else {
672+
let Some((own_stability, attr_span)) = self.reexport_stability_attr(item) else {
638673
return;
639674
};
640675

641-
let (has_target, all_targets_stable) =
642-
self.classify_reexport_targets(path.res.present_items());
676+
let (has_target, all_targets_compatible) =
677+
self.classify_reexport_targets(&own_stability, path.res.present_items());
643678

644-
self.record_unstable_reexport(item, attr_span, path.span, has_target, all_targets_stable);
679+
self.record_reexport_stability(
680+
item,
681+
attr_span,
682+
path.span,
683+
has_target,
684+
all_targets_compatible,
685+
);
645686
}
646687

647-
fn check_glob_unstable_reexport(
688+
fn check_glob_reexport_stability(
648689
&mut self,
649690
item: &'tcx hir::Item<'tcx>,
650691
path: &'tcx UsePath<'tcx>,
651692
) {
652-
let Some(attr_span) = self.unstable_reexport_span(item) else {
693+
let Some((own_stability, attr_span)) = self.reexport_stability_attr(item) else {
653694
return;
654695
};
655696

@@ -669,19 +710,26 @@ impl<'tcx> Checker<'tcx> {
669710
})
670711
.map(|child| child.res);
671712

672-
let (has_target, all_targets_stable) = self.classify_reexport_targets(targets);
713+
let (has_target, all_targets_compatible) =
714+
self.classify_reexport_targets(&own_stability, targets);
673715

674-
self.record_unstable_reexport(item, attr_span, path.span, has_target, all_targets_stable);
716+
self.record_reexport_stability(
717+
item,
718+
attr_span,
719+
path.span,
720+
has_target,
721+
all_targets_compatible,
722+
);
675723
}
676724

677-
fn emit_unused_unstable_reexport_attributes(&self) {
678-
for reexport in self.unstable_reexports.values() {
679-
if reexport.has_target && reexport.all_targets_stable {
725+
fn emit_incompatible_reexport_stability(&self) {
726+
for reexport in self.reexport_stability.values() {
727+
if reexport.has_target && !reexport.all_targets_compatible {
680728
self.tcx.emit_node_span_lint(
681-
UNUSED_UNSTABLE_REEXPORT_ATTRIBUTES,
729+
INCOMPATIBLE_REEXPORT_STABILITY,
682730
reexport.hir_id,
683731
reexport.span,
684-
diagnostics::UnusedUnstableReexportAttributes,
732+
diagnostics::IncompatibleReexportStability,
685733
);
686734
}
687735
}
@@ -718,14 +766,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
718766
if self.tcx.features().staged_api()
719767
&& self.tcx.local_visibility(item.owner_id.def_id).is_public() =>
720768
{
721-
self.check_single_unstable_reexport(item, path);
769+
self.check_single_reexport_stability(item, path);
722770
}
723771

724772
hir::ItemKind::Use(path, hir::UseKind::Glob)
725773
if self.tcx.features().staged_api()
726774
&& self.tcx.local_visibility(item.owner_id.def_id).is_public() =>
727775
{
728-
self.check_glob_unstable_reexport(item, path);
776+
self.check_glob_reexport_stability(item, path);
729777
}
730778

731779
// For implementations of traits, check the stability of each item

library/alloc/src/alloc.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#[stable(feature = "alloc_module", since = "1.28.0")]
66
#[doc(inline)]
7+
#[allow(clippy::useless_attribute)]
8+
#[allow(incompatible_reexport_stability)] // This facade has its own path stability.
79
pub use core::alloc::*;
810
use core::mem::Alignment;
911
use core::ptr::{self, NonNull};

library/alloc/src/collections/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub mod btree_map {
2020
//! An ordered map based on a B-Tree.
2121
#[stable(feature = "rust1", since = "1.0.0")]
2222
#[cfg(not(test))]
23+
#[allow(clippy::useless_attribute)]
24+
#[allow(incompatible_reexport_stability)] // Public facade over internal B-tree items.
2325
pub use super::btree::map::*;
2426
}
2527

@@ -29,6 +31,8 @@ pub mod btree_set {
2931
//! An ordered set based on a B-Tree.
3032
#[stable(feature = "rust1", since = "1.0.0")]
3133
#[cfg(not(test))]
34+
#[allow(clippy::useless_attribute)]
35+
#[allow(incompatible_reexport_stability)] // Public facade over internal B-tree items.
3236
pub use super::btree::set::*;
3337
}
3438

library/alloc/src/fmt.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,10 +600,14 @@ pub use core::fmt::{Arguments, write};
600600
#[stable(feature = "rust1", since = "1.0.0")]
601601
pub use core::fmt::{Binary, Octal};
602602
#[stable(feature = "rust1", since = "1.0.0")]
603+
#[allow(clippy::useless_attribute)]
604+
#[allow(incompatible_reexport_stability)] // This re-export has its own stability.
603605
pub use core::fmt::{Debug, Display};
604606
#[unstable(feature = "formatting_options", issue = "118117")]
605607
pub use core::fmt::{DebugAsHex, FormattingOptions, Sign};
606608
#[stable(feature = "rust1", since = "1.0.0")]
609+
#[allow(clippy::useless_attribute)]
610+
#[allow(incompatible_reexport_stability)] // This re-export has its own stability.
607611
pub use core::fmt::{DebugList, DebugMap, DebugSet, DebugStruct, DebugTuple};
608612
#[stable(feature = "rust1", since = "1.0.0")]
609613
pub use core::fmt::{Formatter, Result, Write};

library/alloc/src/io/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ pub use core::io::const_error;
195195
#[unstable(feature = "core_io_borrowed_buf", issue = "117693")]
196196
pub use core::io::{BorrowedBuf, BorrowedCursor};
197197
#[allow(clippy::useless_attribute)]
198-
#[allow(unused_unstable_reexport_attributes)] // FIXME(#161153)
198+
#[allow(incompatible_reexport_stability)] // FIXME(#161153)
199199
#[unstable(feature = "alloc_io", issue = "154046")]
200200
pub use core::io::{
201201
Chain, Cursor, Empty, Error, ErrorKind, IoSlice, IoSliceMut, Repeat, Result, Seek, SeekFrom,
@@ -212,7 +212,7 @@ use core::io::{
212212
use self::read::{append_to_string, default_read_buf_exact, default_read_exact};
213213
use self::util::{bytes, lines, split, uninlined_slow_read_byte};
214214
#[allow(clippy::useless_attribute)]
215-
#[allow(unused_unstable_reexport_attributes)] // FIXME(#161153)
215+
#[allow(incompatible_reexport_stability)] // FIXME(#161153)
216216
#[unstable(feature = "alloc_io", issue = "154046")]
217217
pub use self::{
218218
buf_read::BufRead,

0 commit comments

Comments
 (0)