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
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