Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
79d5626
lint ineffective #[unstable] annotations on re-exports
amirHdev Aug 16, 2026
7b7695d
handle std fallout from unstable re-export lint
amirHdev Aug 17, 2026
7249fbc
allow clippy on unstable re-export suppressions
amirHdev Aug 17, 2026
e22c3c2
add dedicated lint for ineffective unstable re-exports
amirHdev Aug 18, 2026
fbec8a6
refine unstable reexport lint
amirHdev Aug 19, 2026
b91102a
generalize re-export stability checks
amirHdev Aug 27, 2026
73e6922
fix s390x re-export stability metadata
amirHdev Aug 28, 2026
a246018
fix incompatible reexport stability diagnostics
amirHdev Aug 29, 2026
baaac65
only lint ineffective unstable re-exports
amirHdev Sep 10, 2026
8a0d8f7
avoid depending on Reexport's module path
amirHdev Sep 10, 2026
be9d5a8
fix tidy formatting
amirHdev Sep 10, 2026
db3a0e5
Add regression test for matching associated types on new solver
lsunsi Sep 11, 2026
0aee3f1
Add a UI regression test.
kulinsky Sep 11, 2026
75d7c93
Make the LLVM version-check bindings safe
Zalathar Sep 13, 2026
d8059af
handle unstable modules in ineffective reexport lint
amirHdev Sep 13, 2026
1017887
avoid stability lookup without reexports
amirHdev Sep 13, 2026
dff85fd
Fix typo in riscv64a23-unknown-linux-gnu platform docs
yuefdev Sep 13, 2026
c3c8af4
Rollup merge of #161178 - amirHdev:unstable-reexport, r=mejrs
Zalathar Sep 13, 2026
8ccfa65
Rollup merge of #162642 - lsunsi:issue216, r=nnethercote
Zalathar Sep 13, 2026
6864f88
Rollup merge of #162665 - kulinsky:test-link-section-foreign-static, …
Zalathar Sep 13, 2026
b293ffb
Rollup merge of #162710 - Zalathar:version-safe, r=chenyukang
Zalathar Sep 13, 2026
eefdde7
Rollup merge of #162722 - yuefdev:fix-riscv64a23-doc-typo, r=beetrees
Zalathar Sep 13, 2026
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
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_llvm/src/llvm/enzyme_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ pub(crate) mod Enzyme_AD {
use rustc_session::filesearch;

use super::{CConcreteType, CTypeTreeRef, Context};
use crate::llvm::{EnzymeTypeTree, LLVMRustVersionMajor};
use crate::llvm::{self, EnzymeTypeTree};

type EnzymeSetCLBoolFn = unsafe extern "C" fn(*mut c_void, u8);
type EnzymeSetCLStringFn = unsafe extern "C" fn(*mut c_void, *const c_char);
Expand Down Expand Up @@ -434,7 +434,7 @@ pub(crate) mod Enzyme_AD {
}

fn get_enzyme_path(sysroot: &Sysroot) -> Result<String, EnzymeLibraryError> {
let llvm_version_major = unsafe { LLVMRustVersionMajor() };
let llvm_version_major = llvm::LLVMRustVersionMajor();

let path_buf = sysroot
.all_paths()
Expand Down
10 changes: 7 additions & 3 deletions compiler/rustc_codegen_llvm/src/llvm/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,11 @@ unsafe extern "C" {
SLen: c_uint,
) -> MetadataKindId;

pub(crate) fn LLVMGetVersion(major: &mut c_uint, minor: &mut c_uint, patch: &mut c_uint);
/// Gets the actual version of LLVM that we are linked to at runtime.
///
/// # Safety
/// Can be called without initializing LLVM.
pub(crate) safe fn LLVMGetVersion(major: &mut c_uint, minor: &mut c_uint, patch: &mut c_uint);

pub(crate) fn LLVMDisposeTargetMachine(T: ptr::NonNull<TargetMachine>);

Expand Down Expand Up @@ -2176,9 +2180,9 @@ unsafe extern "C" {
/// Returns the LLVM major version that the compiler was built with.
///
/// Note that this is hard-coded as `LLVM_VERSION_MAJOR` when `RustWrapper.cpp` is built. This
/// could be different than what the runtime LLVM library reports in `LLVMGetVersion`, so we
/// could be different than what the runtime LLVM library reports in [`LLVMGetVersion`], so we
/// assert their equality in `configure_llvm`.
pub(crate) fn LLVMRustVersionMajor() -> u32;
pub(crate) safe fn LLVMRustVersionMajor() -> u32;

/// Add LLVM module flags.
///
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_codegen_llvm/src/llvm/offload_ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rustc_fs_util::path_to_c_string;
use rustc_session::config::host_tuple;
use rustc_session::filesearch;

use crate::llvm::LLVMRustVersionMajor;
use crate::llvm;

pub(crate) struct RustOffloadWrapper {
LLVMRustBundleImages: LLVMRustBundleImagesFn,
Expand Down Expand Up @@ -119,7 +119,7 @@ impl RustOffloadWrapper {
fn get_offload_and_lld_paths(
sysroot: &rustc_session::config::Sysroot,
) -> Result<(PathBuf, Option<PathBuf>), RustOffloadLibraryError> {
let llvm_version_major = unsafe { LLVMRustVersionMajor() };
let llvm_version_major = llvm::LLVMRustVersionMajor();
let mut searched = Vec::new();

for root in sysroot.all_paths() {
Expand Down
14 changes: 6 additions & 8 deletions compiler/rustc_codegen_llvm/src/llvm_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -459,15 +459,13 @@ pub(crate) fn print_version() {
println!("LLVM version: {major}.{minor}.{patch}");
}

/// Returns the version of LLVM that we are actually linked to at runtime.
pub(crate) fn get_version() -> (u32, u32, u32) {
// Can be called without initializing LLVM
unsafe {
let mut llvm_major = 0;
let mut llvm_minor = 0;
let mut llvm_patch = 0;
llvm::LLVMGetVersion(&mut llvm_major, &mut llvm_minor, &mut llvm_patch);
(llvm_major, llvm_minor, llvm_patch)
}
let mut llvm_major = 0;
let mut llvm_minor = 0;
let mut llvm_patch = 0;
llvm::LLVMGetVersion(&mut llvm_major, &mut llvm_minor, &mut llvm_patch);
(llvm_major, llvm_minor, llvm_patch)
}

pub(crate) fn print_passes() {
Expand Down
20 changes: 12 additions & 8 deletions compiler/rustc_lint/src/levels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,14 +969,18 @@ where
let mut lint = Diag::new(dcx, level, msg!("unknown lint: `{$name}`"))
.with_arg("name", lint_id.lint.name_lower())
.with_note(msg!("the `{$name}` lint is unstable"));
rustc_session::diagnostics::add_feature_diagnostics_for_issue(
&mut lint,
sess,
feature,
GateIssue::Language,
lint_from_cli,
None,
);
// `staged_api` is only intended for the standard library, so don't
// suggest enabling it just to use this lint.
if feature != sym::staged_api {
rustc_session::diagnostics::add_feature_diagnostics_for_issue(
&mut lint,
sess,
feature,
GateIssue::Language,
lint_from_cli,
None,
);
}
lint
}
}
Expand Down
33 changes: 33 additions & 0 deletions compiler/rustc_lint_defs/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub mod hardwired {
HIDDEN_GLOB_REEXPORTS,
ILL_FORMED_ATTRIBUTE_INPUT,
INCOMPLETE_INCLUDE,
INEFFECTIVE_UNSTABLE_REEXPORTS,
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
INLINE_NO_SANITIZE,
INVALID_DOC_ATTRIBUTES,
Expand Down Expand Up @@ -2816,6 +2817,38 @@ declare_lint! {
"detects deprecation attributes with no effect",
}

declare_lint! {
/// The `ineffective_unstable_reexports` lint detects `#[unstable]`
/// attributes on re-exports where the attribute does not make the
/// re-exported path unstable.
///
/// ### Example
///
/// ```rust,compile_fail
/// #![feature(staged_api)]
/// #![stable(feature = "test", since = "1.0.0")]
///
/// #[stable(feature = "test", since = "1.0.0")]
/// pub struct S;
///
/// #[unstable(feature = "reexport", issue = "none")]
/// pub use self::S as T;
///
/// fn main() {}
/// ```
///
/// {{produces}}
///
/// ### Explanation
///
/// `#[unstable]` on a re-export does not make a stable path unstable
/// re-exports inside unstable modules are already on an unstable path
pub INEFFECTIVE_UNSTABLE_REEXPORTS,
Deny,
"detects ineffective `#[unstable]` attributes on re-exports",
@feature_gate = staged_api;
}

declare_lint! {
/// The `ineffective_unstable_trait_impl` lint detects `#[unstable]` attributes which are not used.
///
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_passes/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,10 @@ pub(crate) struct UnnecessaryPartialStableFeature {
#[note("see issue #55436 <https://github.com/rust-lang/rust/issues/55436> for more information")]
pub(crate) struct IneffectiveUnstableImpl;

#[derive(Diagnostic)]
#[diag("`#[unstable]` does not make this re-exported path unstable")]
pub(crate) struct IneffectiveUnstableReexport;

// FIXME(jdonszelmann): move back to rustc_attr
#[derive(Diagnostic)]
#[diag(
Expand Down
168 changes: 166 additions & 2 deletions compiler/rustc_passes/src/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ use rustc_hir::{
UsePath, VERSION_PLACEHOLDER, Variant, find_attr,
};
use rustc_lint_defs::builtin::{
DEPRECATED, DUPLICATE_FEATURES, INEFFECTIVE_UNSTABLE_TRAIT_IMPL, STABLE_FEATURES,
DEPRECATED, DUPLICATE_FEATURES, INEFFECTIVE_UNSTABLE_REEXPORTS,
INEFFECTIVE_UNSTABLE_TRAIT_IMPL, STABLE_FEATURES,
};
use rustc_middle::hir::nested_filter;
use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
Expand Down Expand Up @@ -522,7 +523,9 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
/// Cross-references the feature names of unstable APIs with enabled
/// features and possibly prints errors.
fn check_mod_unstable_api_usage(tcx: TyCtxt<'_>, mod_id: LocalModId) {
tcx.hir_visit_item_likes_in_module(mod_id, &mut Checker { tcx });
let mut checker = Checker { tcx, mod_id, unstable_reexports: FxIndexMap::default() };
tcx.hir_visit_item_likes_in_module(mod_id, &mut checker);
checker.emit_ineffective_unstable_reexports();

let is_staged_api =
tcx.sess.opts.unstable_opts.force_unstable_if_unmarked || tcx.features().staged_api();
Expand Down Expand Up @@ -552,8 +555,155 @@ pub(crate) fn provide(providers: &mut Providers) {
};
}

struct UnstableReexport {
hir_id: HirId,
span: Span,
has_target: bool,
all_targets_stable: bool,
}

struct Checker<'tcx> {
tcx: TyCtxt<'tcx>,
mod_id: LocalModId,
unstable_reexports: FxIndexMap<Span, UnstableReexport>,
}

impl<'tcx> Checker<'tcx> {
fn unstable_reexport_span(&self, item: &'tcx hir::Item<'tcx>) -> Option<Span> {
let attrs = self.tcx.hir_attrs(item.hir_id());
let (stability, span) =
find_attr!(attrs, Stability { stability, span } => (*stability, *span))?;

stability.level.is_unstable().then_some(span)
}

fn classify_reexport_targets<Id>(
&self,
targets: impl IntoIterator<Item = Res<Id>>,
) -> (bool, bool) {
let mut has_target = false;
let mut all_targets_stable = true;

for res in targets {
match res {
Res::Def(_, def_id) => {
has_target = true;

match self.tcx.lookup_stability(def_id) {
Some(stability) if stability.level.is_unstable() => {
all_targets_stable = false;
}
Some(_) => {}

None => {
// Items from crates without staged API metadata are
// effectively stable. Unmarked items in staged API
// crates are diagnosed by the existing stability checks.
if self.tcx.lookup_stability(def_id.krate.as_def_id()).is_some() {
all_targets_stable = false;
}
}
}
}

// Primitives are stable and have no DefId.
Res::PrimTy(_) => {
has_target = true;
}

// Do not lint if the target cannot be classified.
_ => {
all_targets_stable = false;
}
}
}

(has_target, all_targets_stable)
}

fn record_unstable_reexport(
&mut self,
item: &'tcx hir::Item<'tcx>,
attr_span: Span,
span: Span,
has_target: bool,
all_targets_stable: bool,
) {
let entry = self.unstable_reexports.entry(attr_span).or_insert(UnstableReexport {
hir_id: item.hir_id(),
span,
has_target: false,
all_targets_stable: true,
});

entry.has_target |= has_target;
entry.all_targets_stable &= all_targets_stable;
}

fn check_single_unstable_reexport(
&mut self,
item: &'tcx hir::Item<'tcx>,
path: &'tcx UsePath<'tcx>,
) {
let Some(attr_span) = self.unstable_reexport_span(item) else {
return;
};

let (has_target, all_targets_stable) =
self.classify_reexport_targets(path.res.present_items());

self.record_unstable_reexport(item, attr_span, path.span, has_target, all_targets_stable);
}

fn check_glob_unstable_reexport(
&mut self,
item: &'tcx hir::Item<'tcx>,
path: &'tcx UsePath<'tcx>,
) {
let Some(attr_span) = self.unstable_reexport_span(item) else {
return;
};

let glob_def_id = item.owner_id.def_id.to_def_id();

let targets = self
.tcx
.module_children_local(self.mod_id.to_local_def_id())
.iter()
.filter(|child| {
child.reexport_chain.iter().any(|reexport| reexport.id() == Some(glob_def_id))
})
.map(|child| child.res);

let (has_target, all_targets_stable) = self.classify_reexport_targets(targets);

self.record_unstable_reexport(item, attr_span, path.span, has_target, all_targets_stable);
}

fn containing_module_is_unstable(&self) -> bool {
self.tcx
.lookup_stability(self.mod_id.to_local_def_id())
.is_some_and(|stability| stability.level.is_unstable())
}

fn emit_ineffective_unstable_reexports(&self) {
// an unstable module already makes its re-exports unstable
// keep the explicit annotation without linting it as ineffective
if self.unstable_reexports.is_empty() || self.containing_module_is_unstable() {
return;
}

for reexport in self.unstable_reexports.values() {
if reexport.has_target && reexport.all_targets_stable {
self.tcx.emit_node_span_lint(
INEFFECTIVE_UNSTABLE_REEXPORTS,
reexport.hir_id,
reexport.span,
diagnostics::IneffectiveUnstableReexport,
);
}
}
}
}

impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
Expand Down Expand Up @@ -582,6 +732,20 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
self.tcx.check_stability(def_id, Some(item.hir_id()), item.span, None);
}

hir::ItemKind::Use(path, hir::UseKind::Single(_))
if self.tcx.features().staged_api()
&& self.tcx.local_visibility(item.owner_id.def_id).is_public() =>
{
self.check_single_unstable_reexport(item, path);
}

hir::ItemKind::Use(path, hir::UseKind::Glob)
if self.tcx.features().staged_api()
&& self.tcx.local_visibility(item.owner_id.def_id).is_public() =>
{
self.check_glob_unstable_reexport(item, path);
}

// For implementations of traits, check the stability of each item
// individually as it's possible to have a stable trait with unstable
// items.
Expand Down
5 changes: 5 additions & 0 deletions library/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,11 @@ pub mod offload;
#[unstable(feature = "contracts", issue = "128044")]
pub mod contracts;

#[allow(clippy::useless_attribute)]
#[expect(
ineffective_unstable_reexports,
reason = "accepted as stable after accidental stabilization in 1.96, see #154645"
)]
#[unstable(feature = "derive_macro_global_path", issue = "154645")]
pub use crate::macros::builtin::derive;
#[stable(feature = "cfg_select", since = "1.95.0")]
Expand Down
2 changes: 1 addition & 1 deletion library/core/src/ops/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ mod unsize;
pub use self::arith::{Add, Div, Mul, Neg, Rem, Sub};
#[stable(feature = "op_assign_traits", since = "1.8.0")]
pub use self::arith::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign};
#[unstable(feature = "async_fn_traits", issue = "none")]
#[stable(feature = "async_closure", since = "1.85.0")]
pub use self::async_function::{AsyncFn, AsyncFnMut, AsyncFnOnce};
#[stable(feature = "rust1", since = "1.0.0")]
pub use self::bit::{BitAnd, BitOr, BitXor, Not, Shl, Shr};
Expand Down
Loading
Loading