From 34562bbf4bc54677b66fe60335525db8dcb56145 Mon Sep 17 00:00:00 2001 From: panstromek Date: Sat, 7 Nov 2026 19:30:34 +0100 Subject: [PATCH 01/12] avoid Terminator:None in mir building by using Either in the builder --- compiler/rustc_mir_build/src/builder/cfg.rs | 49 ++++++++++++++----- .../src/builder/matches/mod.rs | 4 +- compiler/rustc_mir_build/src/builder/mod.rs | 37 ++++++++++++-- compiler/rustc_mir_build/src/builder/scope.rs | 10 ++-- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/cfg.rs b/compiler/rustc_mir_build/src/builder/cfg.rs index 76bb5009a6ec1..b9ff4f2e17635 100644 --- a/compiler/rustc_mir_build/src/builder/cfg.rs +++ b/compiler/rustc_mir_build/src/builder/cfg.rs @@ -1,38 +1,58 @@ //! Routines for manipulating the control-flow graph. +use itertools::Either; use rustc_attr_ir::UnrollAttr; use rustc_data_structures::thin_vec::ThinVec; +use rustc_index::IndexVec; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; use tracing::debug; -use crate::builder::CFG; +use crate::builder::{CFG, CFGBlock, UnterminatedBlockData}; impl<'tcx> CFG<'tcx> { - pub(crate) fn block_data(&self, blk: BasicBlock) -> &BasicBlockData<'tcx> { + pub(crate) fn block_data(&self, blk: BasicBlock) -> &CFGBlock<'tcx> { &self.basic_blocks[blk] } - pub(crate) fn block_data_mut(&mut self, blk: BasicBlock) -> &mut BasicBlockData<'tcx> { + pub(crate) fn block_data_mut(&mut self, blk: BasicBlock) -> &mut CFGBlock<'tcx> { &mut self.basic_blocks[blk] } + pub(crate) fn statements(&self, blk: BasicBlock) -> &[Statement<'tcx>] { + match &self.basic_blocks[blk] { + CFGBlock::Left(bb) => &bb.statements, + CFGBlock::Right(bb) => &bb.statements, + } + } + + pub(crate) fn statements_mut(&mut self, blk: BasicBlock) -> &mut Vec> { + match &mut self.basic_blocks[blk] { + CFGBlock::Left(bb) => &mut bb.statements, + CFGBlock::Right(bb) => &mut bb.statements, + } + } + + pub(crate) fn into_basic_blocks(self) -> IndexVec> { + // CFGBlock has the same layout as BasicBlockData, so this call should be + // almost no-op (we have a specialization for reusing the allocation in this case). + self.basic_blocks.into_iter().map(|bb| bb.left().unwrap()).collect() + } + // llvm.org/PR32488 makes this function use an excess of stack space. Mark // it as #[inline(never)] to keep rustc's stack use in check. #[inline(never)] pub(crate) fn start_new_block(&mut self) -> BasicBlock { - self.basic_blocks.push(BasicBlockData::maybe_unterminated(None, false)) + self.basic_blocks.push(Either::Right(UnterminatedBlockData::new(Vec::new(), false))) } pub(crate) fn start_new_cleanup_block(&mut self) -> BasicBlock { - let bb = self.start_new_block(); - self.block_data_mut(bb).is_cleanup = true; - bb + self.basic_blocks.push(Either::Right(UnterminatedBlockData::new(Vec::new(), true))) } pub(crate) fn push(&mut self, block: BasicBlock, statement: Statement<'tcx>) { debug!("push({:?}, {:?})", block, statement); - self.block_data_mut(block).statements.push(statement); + self.statements_mut(block).push(statement); } pub(crate) fn push_assign( @@ -112,13 +132,20 @@ impl<'tcx> CFG<'tcx> { ) -> &mut Terminator<'tcx> { debug!("terminating block {:?} <- {:?}", block, kind); debug_assert!( - self.block_data(block).terminator.is_none(), + self.block_data(block).is_right(), "terminate: block {:?}={:?} already has a terminator set", block, self.block_data(block) ); - self.block_data_mut(block).terminator = Some(Terminator { source_info, kind }); - self.block_data_mut(block).terminator.as_mut().unwrap() + let UnterminatedBlockData { statements, is_cleanup } = + self.block_data_mut(block).as_mut().right().unwrap(); + + *self.block_data_mut(block) = Either::Left(BasicBlockData::new_stmts( + std::mem::take(statements), + Terminator { source_info, kind }, + *is_cleanup, + )); + self.block_data_mut(block).as_mut().left().unwrap().terminator_mut() } /// In the `origin` block, push a `goto -> target` terminator. diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index 64a1fea013ace..05efc2f4e7138 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -470,8 +470,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { outer_source_info.span.with_lo(outer_source_info.span.hi() - BytePos::from_usize(1)), ); for arm_block in arm_end_blocks { - let block = &self.cfg.basic_blocks[arm_block]; - let last_location = block.statements.last().map(|s| s.source_info); + let statements = self.cfg.statements(arm_block); + let last_location = statements.last().map(|s| s.source_info); self.cfg.goto(arm_block, last_location.unwrap_or(end_brace), end_block); } diff --git a/compiler/rustc_mir_build/src/builder/mod.rs b/compiler/rustc_mir_build/src/builder/mod.rs index 6e8df5e0809ba..21899a0a03a7f 100644 --- a/compiler/rustc_mir_build/src/builder/mod.rs +++ b/compiler/rustc_mir_build/src/builder/mod.rs @@ -20,7 +20,7 @@ //! `let this = self;` or similar, allowing subsequent code to uniformly refer //! to the builder as `this` (and never `self`), even when not nested. -use itertools::Itertools; +use itertools::{Either, Itertools}; use rustc_abi::{ExternAbi, FieldIdx}; use rustc_apfloat::Float; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; @@ -392,8 +392,35 @@ impl LocalsForNode { } } +#[derive(Debug, Clone)] +pub(crate) struct UnterminatedBlockData<'tcx> { + statements: Vec>, + is_cleanup: bool, +} +impl<'tcx> UnterminatedBlockData<'tcx> { + fn new(statements: Vec>, is_cleanup: bool) -> Self { + UnterminatedBlockData { statements, is_cleanup } + } +} + +pub(crate) type CFGBlock<'tcx> = Either, UnterminatedBlockData<'tcx>>; + +#[cfg(target_pointer_width = "64")] +mod size_asserts { + use rustc_data_structures::static_assert_size; + + use super::*; + // Note: It's important that `CFGBlock` has the same layout as `BasicBlockData`. + // because it makes the final transition to `IndexVec>` + // cheaper. If those type layouts differ, we get a perf regression. + // See benchmarks in https://github.com/rust-lang/rust/pull/165611 for more info. + static_assert_size!(BasicBlockData<'_>, 136); + static_assert_size!(CFGBlock<'_>, 136); +} + +#[derive(Debug, Clone)] struct CFG<'tcx> { - basic_blocks: IndexVec>, + basic_blocks: IndexVec>, } #[derive(Debug)] @@ -756,7 +783,7 @@ fn construct_error(tcx: TyCtxt<'_>, def_id: LocalDefId, guar: ErrorGuaranteed) - Body::new( MirSource::item(def_id.to_def_id()), - cfg.basic_blocks, + cfg.into_basic_blocks(), source_scopes, local_decls, IndexVec::new(), @@ -919,7 +946,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { fn dump_for_debugging(&self) { let mut body = Body::new( MirSource::item(self.def_id.to_def_id()), - self.cfg.basic_blocks.clone(), + self.cfg.clone().into_basic_blocks(), self.source_scopes.clone(), self.local_decls.clone(), self.canonical_user_type_annotations.clone(), @@ -938,7 +965,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { fn finish(self) -> Body<'tcx> { let mut body = Body::new( MirSource::item(self.def_id.to_def_id()), - self.cfg.basic_blocks, + self.cfg.into_basic_blocks(), self.source_scopes, self.local_decls, self.canonical_user_type_annotations, diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index fd1215cfdad9b..22e3b3e6dfcc4 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -1621,7 +1621,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn diverge_from(&mut self, start: BasicBlock) { debug_assert!( matches!( - self.cfg.block_data(start).terminator().kind, + self.cfg.block_data(start).as_ref().left().unwrap().terminator().kind, TerminatorKind::Assert { .. } | TerminatorKind::Call { .. } | TerminatorKind::Drop { .. } @@ -1681,7 +1681,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn coroutine_drop_cleanup(&mut self, yield_block: BasicBlock) { debug_assert!( matches!( - self.cfg.block_data(yield_block).terminator().kind, + self.cfg.block_data(yield_block).as_ref().left().unwrap().terminator().kind, TerminatorKind::Yield { .. } ), "coroutine_drop_cleanup called on block with non-yield terminator." @@ -2108,7 +2108,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for ExitScopes { // There should be an existing terminator with real source info and a // dummy TerminatorKind. Replace it with a proper goto. // (The dummy is added by `break_scope` and `break_from_if_then_scope`.) - let term = cfg.block_data_mut(from).terminator_mut(); + let term = cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); if let TerminatorKind::UnwindResume = term.kind { term.kind = TerminatorKind::goto(to); } else { @@ -2124,7 +2124,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for CoroutineDrop { cfg.start_new_block() } fn link_entry_point(cfg: &mut CFG<'tcx>, from: BasicBlock, to: BasicBlock) { - let term = cfg.block_data_mut(from).terminator_mut(); + let term = cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); if let TerminatorKind::Yield { ref mut drop, .. } = term.kind { *drop = Some(to); } else if let TerminatorKind::Drop { ref mut drop, .. } = term.kind { @@ -2146,7 +2146,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { cfg.start_new_cleanup_block() } fn link_entry_point(cfg: &mut CFG<'tcx>, from: BasicBlock, to: BasicBlock) { - let term = &mut cfg.block_data_mut(from).terminator_mut(); + let term = &mut cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); match &mut term.kind { TerminatorKind::Drop { unwind, .. } => { if let UnwindAction::Cleanup(unwind) = *unwind { From 78fda199166913f9c3e34c0fb1ec27f511f82f42 Mon Sep 17 00:00:00 2001 From: vad Date: Fri, 30 Oct 2026 14:48:25 +0100 Subject: [PATCH 02/12] BPF: Define max/min atomic width per CPU version In BPF, available set of atomic instructions depends on CPU version. Make the compiler aware of that distinction by adding the new field `per_cpu_atomic_widths`, then honor its value above `min_atomic_width` and `max_atomic_width`. Previously, we assumed availability of 64-bit acquire/release atomics for BPF, but that's not true for the v3/generic CPU (and older). In effect, `core::io::Error` failed to compile at ISel with `Cannot select: ... AtomicLoad`. https://github.com/rust-lang/rust/issues/135175 proposes making BPF target emit BPF binaries. That requires ability to build entire core library. This change partially fixes the issue. Code in this change is generated by (an open-weight and self-hosted) LLM, it was agreed on in the Zulip thread [0], where @nagisa volunteered as a reviewer. [0] https://rust-lang.zulipchat.com/#narrow/channel/606558-llm-reviews/topic/Seeking.20review.20for.20BPF.20bytecode.20linking.20.28issue.20.23135175.29/near/631892496 --- compiler/rustc_session/src/config/cfg.rs | 6 +- compiler/rustc_target/src/json.rs | 6 + compiler/rustc_target/src/spec/base/bpf.rs | 26 +++- compiler/rustc_target/src/spec/json.rs | 3 + compiler/rustc_target/src/spec/mod.rs | 22 +-- tests/assembly-llvm/bpf-v4-atomics.rs | 148 +++++++++++++++++++++ tests/auxiliary/minicore.rs | 43 ++++++ 7 files changed, 239 insertions(+), 15 deletions(-) create mode 100644 tests/assembly-llvm/bpf-v4-atomics.rs diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs index 7df6549a07a70..4d74cc543b8b8 100644 --- a/compiler/rustc_session/src/config/cfg.rs +++ b/compiler/rustc_session/src/config/cfg.rs @@ -269,6 +269,10 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { sess.dcx().emit_fatal(err); }); let mut has_atomic = false; + let (min_atomic_width, max_atomic_width) = { + let cpu = sess.opts.cg.target_cpu.as_deref().unwrap_or(sess.target.cpu.as_ref()); + sess.target.atomic_widths(cpu) + }; for (i, align) in [ (8, layout.i8_align), (16, layout.i16_align), @@ -276,7 +280,7 @@ pub(crate) fn default_configuration(sess: &Session) -> Cfg { (64, layout.i64_align), (128, layout.i128_align), ] { - if i >= sess.target.min_atomic_width() && i <= sess.target.max_atomic_width() { + if i >= min_atomic_width && i <= max_atomic_width { if !has_atomic { has_atomic = true; if sess.is_nightly_build() { diff --git a/compiler/rustc_target/src/json.rs b/compiler/rustc_target/src/json.rs index 896609bdbe3a7..d99d14bee7055 100644 --- a/compiler/rustc_target/src/json.rs +++ b/compiler/rustc_target/src/json.rs @@ -58,6 +58,12 @@ impl ToJson for [A] { } } +impl ToJson for (T, U) { + fn to_json(&self) -> Json { + Json::Array(vec![self.0.to_json(), self.1.to_json()]) + } +} + impl ToJson for Vec { fn to_json(&self) -> Json { Json::Array(self.iter().map(|elt| elt.to_json()).collect()) diff --git a/compiler/rustc_target/src/spec/base/bpf.rs b/compiler/rustc_target/src/spec/base/bpf.rs index 8d541cb78deac..46cbe65b667fd 100644 --- a/compiler/rustc_target/src/spec/base/bpf.rs +++ b/compiler/rustc_target/src/spec/base/bpf.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use rustc_abi::Endian; use crate::spec::{LinkerFlavor, MergeFunctions, PanicStrategy, TargetOptions}; @@ -19,11 +21,27 @@ pub(crate) fn opts(endian: Endian) -> TargetOptions { merge_functions: MergeFunctions::Disabled, obj_is_bitcode: true, singlethread: true, - // When targeting the `v3` cpu in llvm, 32-bit atomics are also supported. - // But making this value change based on the target cpu can be mostly confusing - // and would require a bit of a refactor. - min_atomic_width: Some(64), + // Atomic loads and stores up to 64 bits are supported from CPU v4. + min_atomic_width: Some(8), max_atomic_width: Some(64), + per_cpu_atomic_widths: BTreeMap::from([ + // CPU versions from v1 to v3 support only read-modify-write atomic + // operations (atomicrmw, cmpxchg), they do not support atomic + // loads and stores. Proof: + // - src/llvm-project/llvm/test/CodeGen/BPF/atomic-load-store.ll + // works only on v4. + // - All other atomic*.ll tests enabled on lower CPU versions don't + // perform loads and stores. + // `Atomic*` types from `core::sync::atomic` require availability + // of both kinds of operations, so they need to be disabled for + // CPU versions lower than v4. BPF developers wanting to use r-m-w + // operations on older kernels/CPUs can call intrinsics directly + // instead of using `core::sync::atomic`. + ("v1".into(), (8, 0)), + ("v2".into(), (8, 0)), + ("v3".into(), (8, 0)), + ("generic".into(), (8, 0)), // generic == v3 + ]), ..Default::default() } } diff --git a/compiler/rustc_target/src/spec/json.rs b/compiler/rustc_target/src/spec/json.rs index 03dbbcb5b481d..87d878a86944e 100644 --- a/compiler/rustc_target/src/spec/json.rs +++ b/compiler/rustc_target/src/spec/json.rs @@ -168,6 +168,7 @@ impl Target { forward!(obj_is_bitcode); forward_opt!(max_atomic_width); forward_opt!(min_atomic_width); + forward!(per_cpu_atomic_widths); forward!(atomic_cas); forward!(panic_strategy); forward!(crt_static_allows_dylibs); @@ -368,6 +369,7 @@ impl ToJson for Target { target_option_val!(obj_is_bitcode); target_option_val!(min_atomic_width); target_option_val!(max_atomic_width); + target_option_val!(per_cpu_atomic_widths); target_option_val!(atomic_cas); target_option_val!(panic_strategy); target_option_val!(crt_static_allows_dylibs); @@ -590,6 +592,7 @@ struct TargetSpecJson { obj_is_bitcode: Option, max_atomic_width: Option, min_atomic_width: Option, + per_cpu_atomic_widths: Option, (u64, u64)>>, atomic_cas: Option, panic_strategy: Option, crt_static_allows_dylibs: Option, diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs index ae17e37e29f86..b3839b23d9a10 100644 --- a/compiler/rustc_target/src/spec/mod.rs +++ b/compiler/rustc_target/src/spec/mod.rs @@ -2433,6 +2433,10 @@ pub struct TargetOptions { /// Don't use this field; instead use the `.max_atomic_width()` method. pub max_atomic_width: Option, + /// Don't use this field; instead use the `.min_atomic_width()` and + /// `max_atomic_width()` methods. + pub per_cpu_atomic_widths: BTreeMap, (u64, u64)>, + /// Whether the target supports atomic CAS operations natively pub atomic_cas: bool, @@ -2807,6 +2811,7 @@ impl Default for TargetOptions { obj_is_bitcode: false, min_atomic_width: None, max_atomic_width: None, + per_cpu_atomic_widths: BTreeMap::new(), atomic_cas: true, panic_strategy: PanicStrategy::Unwind, crt_static_allows_dylibs: false, @@ -2881,16 +2886,13 @@ impl Target { abi_map.canonize_abi(abi, false).is_mapped() } - /// Minimum integer size in bits that this target can perform atomic - /// operations on. - pub fn min_atomic_width(&self) -> u64 { - self.min_atomic_width.unwrap_or(8) - } - - /// Maximum integer size in bits that this target can perform atomic - /// operations on. - pub fn max_atomic_width(&self) -> u64 { - self.max_atomic_width.unwrap_or_else(|| self.pointer_width.into()) + /// Minimum and maximum integer size in bits that this target can perform + /// atomic operations on. + pub fn atomic_widths(&self, cpu: &str) -> (u64, u64) { + self.per_cpu_atomic_widths.get(cpu).copied().unwrap_or(( + self.min_atomic_width.unwrap_or(8), + self.max_atomic_width.unwrap_or_else(|| self.pointer_width.into()), + )) } /// Test target self-consistency and JSON encoding/decoding roundtrip. diff --git a/tests/assembly-llvm/bpf-v4-atomics.rs b/tests/assembly-llvm/bpf-v4-atomics.rs new file mode 100644 index 0000000000000..ef8060302eb69 --- /dev/null +++ b/tests/assembly-llvm/bpf-v4-atomics.rs @@ -0,0 +1,148 @@ +//@ add-minicore +//@ assembly-output: emit-asm +//@ compile-flags: --target bpfel-unknown-none -Ctarget-cpu=v4 +//@ needs-llvm-components: bpf + +// Regression test for 64-bit atomic load/store on BPF. The BPF ISA *does* have +// atomic instructions and LLVM can lower them, but it only selects the 64-bit +// atomic load/store patterns (relaxed included, which lower to plain loads and +// stores) when the subtarget is the `v4` CPU. The target spec pins `cpu = "v4"`, +// so `core::io::Error`'s 64-bit `AtomicPtr` relaxed load/store (the path that +// used to fail at ISel with `Cannot select: ... AtomicLoad`) must lower here. +#![feature(no_core)] +#![crate_type = "rlib"] +#![no_core] + +extern crate minicore; +use minicore::*; + +// Relaxed load/store lower to plain loads/stores (no acquire/release fence). +// CHECK-LABEL: atomic_load_relaxed: +// CHECK: r0 = *(u64 *)(r1 + 0) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_relaxed(p: *const u64) -> u64 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_store_relaxed: +// CHECK: *(u64 *)(r1 + 0) = r2 +// CHECK: exit +#[no_mangle] +pub fn atomic_store_relaxed(p: *mut u64, v: u64) { + unsafe { atomic::atomic_store::(p, v) } +} + +// Acquire load lowers to the `load_acquire` BPF instruction. +// CHECK-LABEL: atomic_load_acquire: +// CHECK: r0 = load_acquire((u64 *)(r1 + 0)) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_acquire(p: *const u64) -> u64 { + unsafe { atomic::atomic_load::(p) } +} + +// Release store lowers to the `store_release` BPF instruction. +// CHECK-LABEL: atomic_store_release: +// CHECK: store_release((u64 *)(r1 + 0), r2) +// CHECK: exit +#[no_mangle] +pub fn atomic_store_release(p: *mut u64, v: u64) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_load_relaxed8: +// CHECK: w0 = *(u8 *)(r1 + 0) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_relaxed8(p: *const u8) -> u8 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_load_acquire8: +// CHECK: w0 = load_acquire((u8 *)(r1 + 0)) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_acquire8(p: *const u8) -> u8 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_store_relaxed8: +// CHECK: *(u8 *)(r1 + 0) = w2 +// CHECK: exit +#[no_mangle] +pub fn atomic_store_relaxed8(p: *mut u8, v: u8) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_store_release8: +// CHECK: store_release((u8 *)(r1 + 0), w2) +// CHECK: exit +#[no_mangle] +pub fn atomic_store_release8(p: *mut u8, v: u8) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_load_relaxed16: +// CHECK: w0 = *(u16 *)(r1 + 0) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_relaxed16(p: *const u16) -> u16 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_load_acquire16: +// CHECK: w0 = load_acquire((u16 *)(r1 + 0)) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_acquire16(p: *const u16) -> u16 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_store_relaxed16: +// CHECK: *(u16 *)(r1 + 0) = w2 +// CHECK: exit +#[no_mangle] +pub fn atomic_store_relaxed16(p: *mut u16, v: u16) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_store_release16: +// CHECK: store_release((u16 *)(r1 + 0), w2) +// CHECK: exit +#[no_mangle] +pub fn atomic_store_release16(p: *mut u16, v: u16) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_load_relaxed32: +// CHECK: w0 = *(u32 *)(r1 + 0) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_relaxed32(p: *const u32) -> u32 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_load_acquire32: +// CHECK: w0 = load_acquire((u32 *)(r1 + 0)) +// CHECK: exit +#[no_mangle] +pub fn atomic_load_acquire32(p: *const u32) -> u32 { + unsafe { atomic::atomic_load::(p) } +} + +// CHECK-LABEL: atomic_store_relaxed32: +// CHECK: *(u32 *)(r1 + 0) = w2 +// CHECK: exit +#[no_mangle] +pub fn atomic_store_relaxed32(p: *mut u32, v: u32) { + unsafe { atomic::atomic_store::(p, v) } +} + +// CHECK-LABEL: atomic_store_release32: +// CHECK: store_release((u32 *)(r1 + 0), w2) +// CHECK: exit +#[no_mangle] +pub fn atomic_store_release32(p: *mut u32, v: u32) { + unsafe { atomic::atomic_store::(p, v) } +} diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index e7e924f9ebc08..f4a6929063c33 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -22,6 +22,7 @@ no_core, intrinsics, lang_items, + adt_const_params, auto_traits, freeze_impls, negative_impls, @@ -381,6 +382,33 @@ trait Drop { #[rustc_intrinsic] pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); +pub enum AtomicOrdering { + Relaxed = 0, + Release = 1, + Acquire = 2, + AcqRel = 3, + SeqCst = 4, +} + +impl ConstParamTy_ for AtomicOrdering {} + +pub mod atomic { + use super::{AtomicOrdering, Copy}; + + #[rustc_nounwind] + #[rustc_intrinsic] + pub const unsafe fn atomic_load( + src: *const T, + ) -> T; + + #[rustc_nounwind] + #[rustc_intrinsic] + pub const unsafe fn atomic_store( + dst: *mut T, + val: T, + ); +} + pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] @@ -490,6 +518,21 @@ impl Copy for Ordering {} #[diagnostic::on_unimplemented(message = "`{Self}` can't be used as a const parameter type")] pub trait ConstParamTy_ {} +impl ConstParamTy_ for usize {} +impl ConstParamTy_ for u8 {} +impl ConstParamTy_ for u16 {} +impl ConstParamTy_ for u32 {} +impl ConstParamTy_ for u64 {} +impl ConstParamTy_ for u128 {} +impl ConstParamTy_ for isize {} +impl ConstParamTy_ for i8 {} +impl ConstParamTy_ for i16 {} +impl ConstParamTy_ for i32 {} +impl ConstParamTy_ for i64 {} +impl ConstParamTy_ for i128 {} +impl ConstParamTy_ for bool {} +impl ConstParamTy_ for char {} + pub enum SimdAlign { // These values must match the compiler's `SimdAlign` defined in // `rustc_middle/src/ty/consts/int.rs`! From 7e4758f8f961c316787823f24733689ac35cb0fb Mon Sep 17 00:00:00 2001 From: Nicole LeGare Date: Mon, 9 Nov 2026 14:27:34 -0800 Subject: [PATCH 03/12] Disable no_threads compiler errors for Trusty targets --- library/std/src/sys/sync/condvar/no_threads.rs | 3 +++ library/std/src/sys/sync/mutex/no_threads.rs | 3 +++ library/std/src/sys/sync/once/no_threads.rs | 3 +++ library/std/src/sys/sync/rwlock/no_threads.rs | 3 +++ library/std/src/sys/thread_local/no_threads.rs | 3 +++ 5 files changed, 15 insertions(+) diff --git a/library/std/src/sys/sync/condvar/no_threads.rs b/library/std/src/sys/sync/condvar/no_threads.rs index 98ca2288f74a2..ff2046b0edcdf 100644 --- a/library/std/src/sys/sync/condvar/no_threads.rs +++ b/library/std/src/sys/sync/condvar/no_threads.rs @@ -8,6 +8,9 @@ use crate::time::Duration; // the main thread, but they are forbidden from accessing sync primitives (the same as UNIX // signal handlers). not(target_os = "vexos"), + // Trusty is "target_has_threads" because it needs atomics for shared-memory IPC, but processes + // can't spawn threads. + not(target_os = "trusty"), ))] compile_error!("Using no_threads implementation on a target with threads"); diff --git a/library/std/src/sys/sync/mutex/no_threads.rs b/library/std/src/sys/sync/mutex/no_threads.rs index d031c0e7e1dcf..6fcbb29cfaaeb 100644 --- a/library/std/src/sys/sync/mutex/no_threads.rs +++ b/library/std/src/sys/sync/mutex/no_threads.rs @@ -6,6 +6,9 @@ use crate::cell::Cell; // the main thread, but they are forbidden from accessing sync primitives (the same as UNIX // signal handlers). not(target_os = "vexos"), + // Trusty is "target_has_threads" because it needs atomics for shared-memory IPC, but processes + // can't spawn threads. + not(target_os = "trusty"), ))] compile_error!("Using no_threads implementation on a target with threads"); diff --git a/library/std/src/sys/sync/once/no_threads.rs b/library/std/src/sys/sync/once/no_threads.rs index 725ec28f7cd4f..c4eb8a775ef6e 100644 --- a/library/std/src/sys/sync/once/no_threads.rs +++ b/library/std/src/sys/sync/once/no_threads.rs @@ -8,6 +8,9 @@ use crate::sync::once::OnceExclusiveState; // the main thread, but they are forbidden from accessing sync primitives (the same as UNIX // signal handlers). not(target_os = "vexos"), + // Trusty is "target_has_threads" because it needs atomics for shared-memory IPC, but processes + // can't spawn threads. + not(target_os = "trusty"), ))] compile_error!("Using no_threads implementation on a target with threads"); diff --git a/library/std/src/sys/sync/rwlock/no_threads.rs b/library/std/src/sys/sync/rwlock/no_threads.rs index e7e845cc785c7..62c893dd56e0e 100644 --- a/library/std/src/sys/sync/rwlock/no_threads.rs +++ b/library/std/src/sys/sync/rwlock/no_threads.rs @@ -6,6 +6,9 @@ use crate::cell::Cell; // the main thread, but they are forbidden from accessing sync primitives (the same as UNIX // signal handlers). not(target_os = "vexos"), + // Trusty is "target_has_threads" because it needs atomics for shared-memory IPC, but processes + // can't spawn threads. + not(target_os = "trusty"), ))] compile_error!("Using no_threads implementation on a target with threads"); diff --git a/library/std/src/sys/thread_local/no_threads.rs b/library/std/src/sys/thread_local/no_threads.rs index c8d40a5f4e5d7..fe77ca61b6f6a 100644 --- a/library/std/src/sys/thread_local/no_threads.rs +++ b/library/std/src/sys/thread_local/no_threads.rs @@ -12,6 +12,9 @@ use crate::ptr; // Since there is only one context that's allowed to access thread locals, it's sound to use the // no_threads implementation. not(target_os = "vexos"), + // Trusty is "target_has_threads" because it needs atomics for shared-memory IPC, but processes + // can't spawn threads. + not(target_os = "trusty"), ))] compile_error!("Using no_threads implementation on a target with threads"); From 98c617ef0eaa1a8209da04dabbb2c620bd62aa42 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 10 Nov 2026 10:49:28 +0100 Subject: [PATCH 04/12] Simplify logic in `warn_on_duplicate_attribute` --- compiler/rustc_builtin_macros/src/util.rs | 42 +++++++++++------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 7b990c5af080f..38ec7e1e5a874 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -27,29 +27,27 @@ pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaI /// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when /// an attribute may have been mistakenly duplicated. pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) { - let attrs: Option<&[Attribute]> = match item { - Annotatable::Item(item) => Some(&item.attrs), - Annotatable::AssocItem(item, _) => Some(&item.attrs), - Annotatable::ForeignItem(item) => Some(&item.attrs), - Annotatable::Expr(expr) => Some(&expr.attrs), - Annotatable::Arm(arm) => Some(&arm.attrs), - Annotatable::ExprField(field) => Some(&field.attrs), - Annotatable::PatField(field) => Some(&field.attrs), - Annotatable::GenericParam(param) => Some(¶m.attrs), - Annotatable::Param(param) => Some(¶m.attrs), - Annotatable::FieldDef(def) => Some(&def.attrs), - Annotatable::Variant(variant) => Some(&variant.attrs), - _ => None, + let attrs: &[Attribute] = match item { + Annotatable::Item(item) => &item.attrs, + Annotatable::AssocItem(item, _) => &item.attrs, + Annotatable::ForeignItem(item) => &item.attrs, + Annotatable::Expr(expr) => &expr.attrs, + Annotatable::Arm(arm) => &arm.attrs, + Annotatable::ExprField(field) => &field.attrs, + Annotatable::PatField(field) => &field.attrs, + Annotatable::GenericParam(param) => ¶m.attrs, + Annotatable::Param(param) => ¶m.attrs, + Annotatable::FieldDef(def) => &def.attrs, + Annotatable::Variant(variant) => &variant.attrs, + _ => return, }; - if let Some(attrs) = attrs { - if let Some(attr) = attr::find_by_name(attrs, name) { - ecx.psess().buffer_lint( - DUPLICATE_MACRO_ATTRIBUTES, - attr.span, - ecx.current_expansion.lint_node_id, - diagnostics::DuplicateMacroAttribute, - ); - } + if let Some(attr) = attr::find_by_name(attrs, name) { + ecx.psess().buffer_lint( + DUPLICATE_MACRO_ATTRIBUTES, + attr.span, + ecx.current_expansion.lint_node_id, + diagnostics::DuplicateMacroAttribute, + ); } } From f07a4aae0a3448b361e50c426df02672203353d1 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 10 Nov 2026 11:31:08 +0100 Subject: [PATCH 05/12] Add regression test with multiple diagnostics --- tests/ui/attributes/duplicated-attributes.rs | 7 +++++++ tests/ui/attributes/duplicated-attributes.stderr | 14 +++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/ui/attributes/duplicated-attributes.rs b/tests/ui/attributes/duplicated-attributes.rs index 65cab297af715..da86a4a2256e1 100644 --- a/tests/ui/attributes/duplicated-attributes.rs +++ b/tests/ui/attributes/duplicated-attributes.rs @@ -30,6 +30,13 @@ use test::Bencher; //~^ WARNING duplicated attribute fn f4(_: &mut Bencher) {} +#[test] +#[test] +//~^ WARNING duplicated attribute +#[test] +//~^ WARNING duplicated attribute +fn f5() {} + #[cfg_eval] #[cfg_eval] //~^ WARNING duplicated attribute diff --git a/tests/ui/attributes/duplicated-attributes.stderr b/tests/ui/attributes/duplicated-attributes.stderr index 735d950b27c22..7fc38e10226e4 100644 --- a/tests/ui/attributes/duplicated-attributes.stderr +++ b/tests/ui/attributes/duplicated-attributes.stderr @@ -15,8 +15,20 @@ LL | #[bench] warning: duplicated attribute --> $DIR/duplicated-attributes.rs:34:1 | +LL | #[test] + | ^^^^^^^ + +warning: duplicated attribute + --> $DIR/duplicated-attributes.rs:36:1 + | +LL | #[test] + | ^^^^^^^ + +warning: duplicated attribute + --> $DIR/duplicated-attributes.rs:41:1 + | LL | #[cfg_eval] | ^^^^^^^^^^^ -warning: 3 warnings emitted +warning: 5 warnings emitted From 75ac59620282da63ce1ee473d358fabea361b8b4 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 10 Nov 2026 11:35:05 +0100 Subject: [PATCH 06/12] Use the nicer `UnusedMultiple` diagnostic from `rustc_attr_parsing` --- .../rustc_attr_parsing/src/diagnostics.rs | 3 +- compiler/rustc_attr_parsing/src/lib.rs | 2 +- compiler/rustc_builtin_macros/src/cfg_eval.rs | 4 +- .../rustc_builtin_macros/src/diagnostics.rs | 4 -- compiler/rustc_builtin_macros/src/test.rs | 6 +-- compiler/rustc_builtin_macros/src/util.rs | 11 +++-- tests/ui/attributes/duplicated-attributes.rs | 10 ++--- .../attributes/duplicated-attributes.stderr | 41 ++++++++++++++++--- 8 files changed, 55 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 8c2889e90f4c0..9301780ee734c 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -1335,10 +1335,9 @@ pub(crate) struct UnknownVersionLiteral { pub span: Span, } -// FIXME(jdonszelmann) duplicated from `rustc_passes`, remove once `check_attr` is integrated. #[derive(Diagnostic)] #[diag("multiple `{$name}` attributes")] -pub(crate) struct UnusedMultiple { +pub struct UnusedMultiple { #[primary_span] #[suggestion("remove this attribute", code = "", applicability = "unspecified")] pub this: Span, diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 4e6b8b2d38f3b..e749fce53bd14 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -118,7 +118,7 @@ pub use attributes::cfg::{ pub use attributes::cfg_select::*; pub use attributes::util::{is_builtin_attr, parse_version}; pub use context::ShouldEmit; -pub use diagnostics::ParsedDescription; +pub use diagnostics::{ParsedDescription, UnusedMultiple}; pub use interface::{AttributeParser, EmitAttribute}; pub use rustc_parse::parser::Recovery; pub use template::AttributeTemplate; diff --git a/compiler/rustc_builtin_macros/src/cfg_eval.rs b/compiler/rustc_builtin_macros/src/cfg_eval.rs index 0aa3a52fdd808..210b9ee895c0f 100644 --- a/compiler/rustc_builtin_macros/src/cfg_eval.rs +++ b/compiler/rustc_builtin_macros/src/cfg_eval.rs @@ -19,12 +19,12 @@ use crate::util::{check_builtin_macro_attribute, warn_on_duplicate_attribute}; pub(crate) fn expand( ecx: &mut ExtCtxt<'_>, - _span: Span, + span: Span, meta_item: &ast::MetaItem, annotatable: Annotatable, ) -> Vec { check_builtin_macro_attribute(ecx, meta_item, sym::cfg_eval); - warn_on_duplicate_attribute(ecx, &annotatable, sym::cfg_eval); + warn_on_duplicate_attribute(ecx, &annotatable, span, sym::cfg_eval); vec![cfg_eval(ecx.sess, ecx.ecfg.features, annotatable, ecx.current_expansion.lint_node_id)] } diff --git a/compiler/rustc_builtin_macros/src/diagnostics.rs b/compiler/rustc_builtin_macros/src/diagnostics.rs index d85bb972f4a86..30c332109df5c 100644 --- a/compiler/rustc_builtin_macros/src/diagnostics.rs +++ b/compiler/rustc_builtin_macros/src/diagnostics.rs @@ -22,10 +22,6 @@ pub(crate) struct IncompleteInclude; #[diag("cannot test inner items")] pub(crate) struct UnnameableTestItems; -#[derive(Diagnostic)] -#[diag("duplicated attribute")] -pub(crate) struct DuplicateMacroAttribute; - #[derive(Diagnostic)] #[diag("macro requires a cfg-pattern as an argument")] pub(crate) struct RequiresCfgPattern { diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 5f86842ae75e8..b54ae300b4d80 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -30,7 +30,7 @@ pub(crate) fn expand_test_case( anno_item: Annotatable, ) -> Vec { check_builtin_macro_attribute(ecx, meta_item, sym::test_case); - warn_on_duplicate_attribute(ecx, &anno_item, sym::test_case); + warn_on_duplicate_attribute(ecx, &anno_item, attr_sp, sym::test_case); let sp = ecx.with_def_site_ctxt(attr_sp); let (mut item, is_stmt) = match anno_item { @@ -88,7 +88,7 @@ pub(crate) fn expand_test( item: Annotatable, ) -> Vec { check_builtin_macro_attribute(cx, meta_item, sym::test); - warn_on_duplicate_attribute(cx, &item, sym::test); + warn_on_duplicate_attribute(cx, &item, attr_sp, sym::test); expand_test_or_bench(cx, attr_sp, item, false) } @@ -99,7 +99,7 @@ pub(crate) fn expand_bench( item: Annotatable, ) -> Vec { check_builtin_macro_attribute(cx, meta_item, sym::bench); - warn_on_duplicate_attribute(cx, &item, sym::bench); + warn_on_duplicate_attribute(cx, &item, attr_path_sp, sym::bench); expand_test_or_bench(cx, attr_path_sp, item, true) } diff --git a/compiler/rustc_builtin_macros/src/util.rs b/compiler/rustc_builtin_macros/src/util.rs index 38ec7e1e5a874..903f08e2b7ebd 100644 --- a/compiler/rustc_builtin_macros/src/util.rs +++ b/compiler/rustc_builtin_macros/src/util.rs @@ -1,6 +1,6 @@ use rustc_ast::tokenstream::TokenStream; use rustc_ast::{self as ast, AttrStyle, Attribute, MetaItem, attr, token}; -use rustc_attr_parsing::{AttributeTemplate, validate_attr}; +use rustc_attr_parsing::{AttributeTemplate, UnusedMultiple, validate_attr}; use rustc_errors::{Applicability, Diag, ErrorGuaranteed}; use rustc_expand::base::{Annotatable, ExpandResult, ExtCtxt}; use rustc_expand::expand::AstFragment; @@ -26,7 +26,12 @@ pub(crate) fn check_builtin_macro_attribute(ecx: &ExtCtxt<'_>, meta_item: &MetaI /// Emit a warning if the item is annotated with the given attribute. This is used to diagnose when /// an attribute may have been mistakenly duplicated. -pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, name: Symbol) { +pub(crate) fn warn_on_duplicate_attribute( + ecx: &ExtCtxt<'_>, + item: &Annotatable, + span: Span, + name: Symbol, +) { let attrs: &[Attribute] = match item { Annotatable::Item(item) => &item.attrs, Annotatable::AssocItem(item, _) => &item.attrs, @@ -46,7 +51,7 @@ pub(crate) fn warn_on_duplicate_attribute(ecx: &ExtCtxt<'_>, item: &Annotatable, DUPLICATE_MACRO_ATTRIBUTES, attr.span, ecx.current_expansion.lint_node_id, - diagnostics::DuplicateMacroAttribute, + UnusedMultiple { this: attr.span, other: span, name }, ); } } diff --git a/tests/ui/attributes/duplicated-attributes.rs b/tests/ui/attributes/duplicated-attributes.rs index da86a4a2256e1..0b5840d08da17 100644 --- a/tests/ui/attributes/duplicated-attributes.rs +++ b/tests/ui/attributes/duplicated-attributes.rs @@ -10,7 +10,7 @@ #[test] #[test] -//~^ WARNING duplicated attribute +//~^ WARNING multiple `test` attributes fn f() {} // The following shouldn't trigger an error. The attribute is not duplicated. @@ -27,19 +27,19 @@ use test::Bencher; #[bench] #[bench] -//~^ WARNING duplicated attribute +//~^ WARNING multiple `bench` attributes fn f4(_: &mut Bencher) {} #[test] #[test] -//~^ WARNING duplicated attribute +//~^ WARNING multiple `test` attributes #[test] -//~^ WARNING duplicated attribute +//~^ WARNING multiple `test` attributes fn f5() {} #[cfg_eval] #[cfg_eval] -//~^ WARNING duplicated attribute +//~^ WARNING multiple `cfg_eval` attributes struct S; #[cfg_eval] diff --git a/tests/ui/attributes/duplicated-attributes.stderr b/tests/ui/attributes/duplicated-attributes.stderr index 7fc38e10226e4..dbffb9a5ef470 100644 --- a/tests/ui/attributes/duplicated-attributes.stderr +++ b/tests/ui/attributes/duplicated-attributes.stderr @@ -1,32 +1,61 @@ -warning: duplicated attribute +warning: multiple `test` attributes --> $DIR/duplicated-attributes.rs:12:1 | LL | #[test] - | ^^^^^^^ + | ^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/duplicated-attributes.rs:11:1 | +LL | #[test] + | ^^^^^^^ = note: `#[warn(duplicate_macro_attributes)]` on by default -warning: duplicated attribute +warning: multiple `bench` attributes --> $DIR/duplicated-attributes.rs:29:1 | +LL | #[bench] + | ^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/duplicated-attributes.rs:28:1 + | LL | #[bench] | ^^^^^^^^ -warning: duplicated attribute +warning: multiple `test` attributes --> $DIR/duplicated-attributes.rs:34:1 | +LL | #[test] + | ^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/duplicated-attributes.rs:33:1 + | LL | #[test] | ^^^^^^^ -warning: duplicated attribute +warning: multiple `test` attributes --> $DIR/duplicated-attributes.rs:36:1 | +LL | #[test] + | ^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/duplicated-attributes.rs:34:1 + | LL | #[test] | ^^^^^^^ -warning: duplicated attribute +warning: multiple `cfg_eval` attributes --> $DIR/duplicated-attributes.rs:41:1 | +LL | #[cfg_eval] + | ^^^^^^^^^^^ help: remove this attribute + | +note: attribute also specified here + --> $DIR/duplicated-attributes.rs:40:1 + | LL | #[cfg_eval] | ^^^^^^^^^^^ From 5baf366bc3ca85c404a8c4813c6e3b5a568a86ef Mon Sep 17 00:00:00 2001 From: Makai Date: Tue, 10 Nov 2026 12:56:08 +0000 Subject: [PATCH 07/12] Support underscore as associated const name in inherent impls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: León Orell Valerian Liehr --- .../rustc_ast_passes/src/ast_validation.rs | 14 +++- compiler/rustc_feature/src/unstable.rs | 2 + compiler/rustc_hir/src/hir.rs | 7 ++ compiler/rustc_hir_analysis/src/check/mod.rs | 5 +- .../src/coherence/inherent_impls_overlap.rs | 8 +- .../src/hir_ty_lowering/errors.rs | 6 +- .../src/fn_ctxt/suggestions.rs | 2 +- compiler/rustc_hir_typeck/src/method/probe.rs | 8 +- compiler/rustc_metadata/src/rmeta/decoder.rs | 9 ++- compiler/rustc_metadata/src/rmeta/encoder.rs | 4 + compiler/rustc_metadata/src/rmeta/mod.rs | 1 + compiler/rustc_middle/src/ty/assoc.rs | 36 +++++++-- compiler/rustc_passes/src/dead.rs | 11 +++ compiler/rustc_public/src/ty/tys.rs | 8 +- .../src/unstable/convert/stable/ty.rs | 9 ++- compiler/rustc_span/src/symbol.rs | 1 + .../src/traits/dyn_compatibility.rs | 5 +- compiler/rustc_ty_utils/src/assoc.rs | 13 +++- src/librustdoc/clean/inline.rs | 1 + src/librustdoc/clean/mod.rs | 4 +- .../clippy_lints/src/assigning_clones.rs | 2 +- .../associated-const-underscore.rs | 11 +++ .../associated-const-underscore.rs | 11 +++ .../associated-const-underscore-dead-code.rs | 13 ++++ ...sociated-const-underscore-dead-code.stderr | 14 ++++ .../associated-const-underscore-fail.rs | 41 ++++++++++ .../associated-const-underscore-fail.stderr | 48 ++++++++++++ .../associated-const-underscore-visibility.rs | 20 +++++ ...ociated-const-underscore-visibility.stderr | 19 +++++ .../associated-const-underscore.rs | 75 +++++++++++++++++++ ...eature-gate-associated-const-underscore.rs | 10 +++ ...re-gate-associated-const-underscore.stderr | 13 ++++ .../assoc-const-underscore-semantic-fail.rs | 7 +- ...ssoc-const-underscore-semantic-fail.stderr | 14 +--- 34 files changed, 406 insertions(+), 46 deletions(-) create mode 100644 tests/rustdoc-html/associated-const-underscore.rs create mode 100644 tests/rustdoc-json/associated-const-underscore.rs create mode 100644 tests/ui/associated-consts/associated-const-underscore-dead-code.rs create mode 100644 tests/ui/associated-consts/associated-const-underscore-dead-code.stderr create mode 100644 tests/ui/associated-consts/associated-const-underscore-fail.rs create mode 100644 tests/ui/associated-consts/associated-const-underscore-fail.stderr create mode 100644 tests/ui/associated-consts/associated-const-underscore-visibility.rs create mode 100644 tests/ui/associated-consts/associated-const-underscore-visibility.stderr create mode 100644 tests/ui/associated-consts/associated-const-underscore.rs create mode 100644 tests/ui/feature-gates/feature-gate-associated-const-underscore.rs create mode 100644 tests/ui/feature-gates/feature-gate-associated-const-underscore.stderr diff --git a/compiler/rustc_ast_passes/src/ast_validation.rs b/compiler/rustc_ast_passes/src/ast_validation.rs index a6e88f21ba74f..4354adb188e3b 100644 --- a/compiler/rustc_ast_passes/src/ast_validation.rs +++ b/compiler/rustc_ast_passes/src/ast_validation.rs @@ -1071,11 +1071,19 @@ impl<'a> AstValidator<'a> { } } - fn check_item_named(&self, ident: Ident, kind: &str) { + fn check_item_named(&self, ident: Ident, kind: &str, ctxt: AssocCtxt) { if ident.name != kw::Underscore { return; } - self.dcx().emit_err(diagnostics::ItemUnderscore { span: ident.span, kind }); + + if !self.features.associated_const_underscore() + && matches!(ctxt, AssocCtxt::Impl { of_trait: false }) + { + let msg = format!("naming associated constants with `_` is unstable"); + feature_err(&self.sess, sym::associated_const_underscore, ident.span, msg).emit(); + } else if !matches!(ctxt, AssocCtxt::Impl { of_trait: false }) { + self.dcx().emit_err(diagnostics::ItemUnderscore { span: ident.span, kind }); + } } fn check_nomangle_item_asciionly(&self, ident: Ident, item_span: Span) { @@ -2162,7 +2170,7 @@ impl Visitor<'_> for AstValidator<'_> { } if let AssocItemKind::Const(ci) = &item.kind { - self.check_item_named(ci.ident, "const"); + self.check_item_named(ci.ident, "const", ctxt); } let parent_is_const = diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index ed2bf64263c67..5de5d0a9a0b7e 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -390,6 +390,8 @@ declare_features! ( (unstable, asm_goto_with_outputs, "1.85.0", Some(119364)), /// Allows the `may_unwind` option in inline assembly. (unstable, asm_unwind, "1.58.0", Some(93334)), + /// Allows `_` for the name of associated constants. + (unstable, associated_const_underscore, "CURRENT_RUSTC_VERSION", Some(158944)), /// Allows associated type defaults. (unstable, associated_type_defaults, "1.2.0", Some(29661)), /// Allows implementing `AsyncDrop`. diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 4ce8f5e793bc9..779b049063b8f 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3144,6 +3144,13 @@ impl<'hir> ImplItem<'hir> { ImplItemId { owner_id: self.owner_id } } + /// Returns whether this is an anonymous associated constant in an inherent impl. + pub fn is_anon_const(&self) -> bool { + matches!(self.impl_kind, ImplItemImplKind::Inherent { .. }) + && matches!(self.kind, ImplItemKind::Const(..)) + && self.ident.name == kw::Underscore + } + pub fn vis_span(&self) -> Option { match self.impl_kind { ImplItemImplKind::Trait { .. } => None, diff --git a/compiler/rustc_hir_analysis/src/check/mod.rs b/compiler/rustc_hir_analysis/src/check/mod.rs index 02acd8dc58918..b063a8e68422d 100644 --- a/compiler/rustc_hir_analysis/src/check/mod.rs +++ b/compiler/rustc_hir_analysis/src/check/mod.rs @@ -614,7 +614,10 @@ fn suggestion_signature<'tcx>( ); format!("type {}{generics} = /* Type */{where_clauses};", assoc.name()) } - ty::AssocKind::Const { name, .. } => { + ty::AssocKind::Const { data: ty::AssocConstData::Anonymous } => { + panic!("anonymous associated consts shouldn't end up here") + } + ty::AssocKind::Const { data: ty::AssocConstData::Named(name) } => { let ty = tcx.type_of(assoc.def_id).instantiate_identity().skip_norm_wip(); let val = tcx .infer_ctxt() diff --git a/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs b/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs index 05ceb049e0dba..576300b5eaf1f 100644 --- a/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs +++ b/compiler/rustc_hir_analysis/src/coherence/inherent_impls_overlap.rs @@ -49,7 +49,7 @@ impl<'tcx> InherentOverlapChecker<'tcx> { std::mem::swap(&mut impl_items1, &mut impl_items2); } - for &item1 in impl_items1.in_definition_order() { + for &item1 in impl_items1.named_items_in_definition_order() { let collision = impl_items2 .filter_by_name_unhygienic(item1.name()) .any(|&item2| self.compare_hygienically(item1, item2)); @@ -74,7 +74,7 @@ impl<'tcx> InherentOverlapChecker<'tcx> { let mut seen_items = FxIndexMap::default(); let mut res = Ok(()); - for impl_item in impl_items.in_definition_order() { + for &impl_item in impl_items.named_items_in_definition_order() { let span = self.tcx.def_span(impl_item.def_id); let ident = impl_item.ident(self.tcx); @@ -111,7 +111,7 @@ impl<'tcx> InherentOverlapChecker<'tcx> { let impl_items2 = self.tcx.associated_items(impl2); let mut res = Ok(()); - for &item1 in impl_items1.in_definition_order() { + for &item1 in impl_items1.named_items_in_definition_order() { let collision = impl_items2 .filter_by_name_unhygienic(item1.name()) .find(|&&item2| self.compare_hygienically(item1, item2)); @@ -228,7 +228,7 @@ impl<'tcx> InherentOverlapChecker<'tcx> { // First obtain a list of existing connected region ids let mut idents_to_add = SmallVec::<[Symbol; 8]>::new(); let mut ids = impl_items - .in_definition_order() + .named_items_in_definition_order() .filter_map(|item| { let entry = connected_region_ids.entry(item.name()); if let IndexEntry::Occupied(e) = &entry { diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs index 83f924b9e374c..0488c378b934c 100644 --- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs +++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs @@ -205,10 +205,10 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ { let wider_candidate_names: Vec<_> = visible_traits .iter() - .flat_map(|trait_def_id| tcx.associated_items(*trait_def_id).in_definition_order()) - .filter_map(|item| { - (!item.is_impl_trait_in_trait() && item.tag() == assoc_tag).then(|| item.name()) + .flat_map(|trait_def_id| { + tcx.associated_items(*trait_def_id).named_items_in_definition_order() }) + .filter_map(|item| (item.tag() == assoc_tag).then(|| item.name())) .collect(); if let Some(suggested_name) = diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 40f48898ec658..67a692b07a2ca 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -283,7 +283,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let Some(iterator_item_id) = tcx .associated_items(iterator_trait_id) - .in_definition_order() + .named_items_in_definition_order() .find(|item| item.name() == sym::Item) .map(|item| item.def_id) else { diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index b20115a6df556..f6cfe6faf7a79 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1894,7 +1894,10 @@ impl<'tcx> Pick<'tcx> { tcx.def_path_str(this.item.def_id), )); } - (ty::AssocKind::Const { name, .. }, ty::AssocContainer::Trait) => { + ( + ty::AssocKind::Const { data: ty::AssocConstData::Named(name) }, + ty::AssocContainer::Trait, + ) => { let def_id = this.item.container_id(tcx); lint.span_suggestion( span, @@ -2662,7 +2665,6 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { { return true; } - false } @@ -2676,7 +2678,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { let max_dist = max(name.as_str().len(), 3) / 3; self.tcx .associated_items(def_id) - .in_definition_order() + .named_items_in_definition_order() .filter(|x| { if !self.is_relevant_kind_for_mode(x.kind) { return false; diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 14f5f4683e970..cec1c7c092186 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -1444,7 +1444,14 @@ impl CrateMetadata { fn get_associated_item(&self, tcx: TyCtxt<'_>, id: DefIndex) -> ty::AssocItem { let kind = match self.def_kind(id) { - DefKind::AssocConst => ty::AssocKind::Const { name: self.item_name(id) }, + DefKind::AssocConst => { + let data = if let Some(item_name) = self.root.tables.opt_item_name.get(self, id) { + ty::AssocConstData::Named(item_name.decode((self, tcx))) + } else { + ty::AssocConstData::Anonymous + }; + ty::AssocKind::Const { data } + } DefKind::AssocFn => ty::AssocKind::Fn { name: self.item_name(id), has_self: self.get_fn_has_self_parameter(tcx, id), diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 24fecc6014c80..3385f33b64989 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1898,6 +1898,10 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { self.table_writer().assoc_container(def_id, item.container); + if let Some(item_name) = item.opt_name() { + self.table_writer().opt_item_name(def_id, Some(item_name)); + } + if let AssocContainer::Trait = item.container && item.is_type() { diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 62999889515cb..3706d78613619 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -641,6 +641,7 @@ define_tables! { explicit_implied_clauses_of: Table, Span)>>, explicit_implied_const_bounds: Table, Span)>>, inherent_impls: Table>, + opt_item_name: Table>>, // Reexported names are not associated with individual `DefId`s, // e.g. a glob import can introduce a lot of names, all with the same `DefId`. // That's why the encoded list needs to contain `ModChild` structures describing all the names diff --git a/compiler/rustc_middle/src/ty/assoc.rs b/compiler/rustc_middle/src/ty/assoc.rs index e52d74f4c23fe..05635c9e2ce26 100644 --- a/compiler/rustc_middle/src/ty/assoc.rs +++ b/compiler/rustc_middle/src/ty/assoc.rs @@ -31,15 +31,16 @@ impl AssocItem { match self.kind { ty::AssocKind::Type { data: AssocTypeData::Normal(name) } => Some(name), ty::AssocKind::Type { data: AssocTypeData::Rpitit(_) } => None, - ty::AssocKind::Const { name, .. } => Some(name), + ty::AssocKind::Const { data: AssocConstData::Named(name), .. } => Some(name), + ty::AssocKind::Const { data: AssocConstData::Anonymous, .. } => None, ty::AssocKind::Fn { name, .. } => Some(name), } } - // Gets the identifier name. Aborts if it lacks one, i.e. is an RPITIT - // associated type. + // Gets the identifier name. Aborts if it lacks one, e.g. for an RPITIT + // associated type or an anonymous associated const. pub fn name(&self) -> Symbol { - self.opt_name().expect("name of non-Rpitit assoc item") + self.opt_name().expect("name of anonymous associated item") } pub fn ident(&self, tcx: TyCtxt<'_>) -> Ident { @@ -120,7 +121,11 @@ impl AssocItem { tcx.fn_sig(self.def_id).instantiate_identity().skip_binder().to_string() } ty::AssocKind::Type { .. } => format!("type {};", self.name()), - ty::AssocKind::Const { name, .. } => { + ty::AssocKind::Const { data, .. } => { + let name = match data { + AssocConstData::Named(name) => name, + AssocConstData::Anonymous => rustc_span::symbol::kw::Underscore, + }; format!("const {}: {:?};", name, tcx.type_of(self.def_id).instantiate_identity()) } } @@ -149,6 +154,10 @@ impl AssocItem { } } + pub fn is_anon_const(&self) -> bool { + matches!(self.kind, ty::AssocKind::Const { data: AssocConstData::Anonymous, .. }) + } + pub fn is_fn(&self) -> bool { matches!(self.kind, ty::AssocKind::Fn { .. }) } @@ -179,9 +188,16 @@ pub enum AssocTypeData { Rpitit(ty::ImplTraitInTraitData), } +#[derive(Copy, Clone, PartialEq, Debug, StableHash, Eq, Hash, Encodable, Decodable)] +pub enum AssocConstData { + Named(Symbol), + /// An associated constant named `_`, which *semantically* has no name. + Anonymous, +} + #[derive(Copy, Clone, PartialEq, Debug, StableHash, Eq, Hash, Encodable, Decodable)] pub enum AssocKind { - Const { name: Symbol }, + Const { data: AssocConstData }, Fn { name: Symbol, has_self: bool }, Type { data: AssocTypeData }, } @@ -268,6 +284,14 @@ impl AssocItems { self.items.iter().map(|(_, v)| v) } + /// Returns named associated items in definition order. + /// + /// New code should avoid relying on definition order. If you need a particular associated item + /// for a known trait, make that trait a lang item instead of indexing this array. + pub fn named_items_in_definition_order(&self) -> impl '_ + Iterator { + self.items.iter().filter_map(|(name, v)| name.is_some().then(|| v)) + } + pub fn len(&self) -> usize { self.items.len() } diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index ffa3a7443ecc4..8513d3631248b 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -954,6 +954,17 @@ fn maybe_record_as_seed<'tcx>( } } DefKind::AssocFn | DefKind::AssocConst | DefKind::AssocTy => { + // As with free constants named `_`, associated constants named `_` are always live. + if let hir::Node::ImplItem(impl_item) = tcx.hir_node_by_def_id(owner_id.def_id) + && impl_item.is_anon_const() + { + push_into_worklist(WorkItem { + id: owner_id.def_id, + propagated: ComesFromAllowExpect::No, + own: ComesFromAllowExpect::No, + }); + } + if allow_dead_code.is_none() { let parent = tcx.local_parent(owner_id.def_id); match tcx.def_kind(parent) { diff --git a/compiler/rustc_public/src/ty/tys.rs b/compiler/rustc_public/src/ty/tys.rs index 179d969a53675..6a41392d1c3e4 100644 --- a/compiler/rustc_public/src/ty/tys.rs +++ b/compiler/rustc_public/src/ty/tys.rs @@ -1374,9 +1374,15 @@ pub enum AssocTypeData { Rpitit(ImplTraitInTraitData), } +#[derive(Clone, PartialEq, Debug, Eq, Serialize)] +pub enum AssocConstData { + Named(Symbol), + Anonymous, +} + #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub enum AssocKind { - Const { name: Symbol }, + Const { data: AssocConstData }, Fn { name: Symbol, has_self: bool }, Type { data: AssocTypeData }, } diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index 51dd03a252693..f4f461980023e 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -1075,9 +1075,14 @@ impl<'tcx> Stable<'tcx> for ty::AssocKind { tables: &mut Tables<'cx, BridgeTys>, cx: &CompilerCtxt<'cx, BridgeTys>, ) -> Self::T { - use crate::ty::{AssocKind, AssocTypeData}; + use crate::ty::{AssocConstData, AssocKind, AssocTypeData}; match *self { - ty::AssocKind::Const { name, .. } => AssocKind::Const { name: name.to_string() }, + ty::AssocKind::Const { data, .. } => AssocKind::Const { + data: match data { + ty::AssocConstData::Named(name) => AssocConstData::Named(name.to_string()), + ty::AssocConstData::Anonymous => AssocConstData::Anonymous, + }, + }, ty::AssocKind::Fn { name, has_self } => { AssocKind::Fn { name: name.to_string(), has_self } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 2170f610f0939..a96e8b597d77a 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -479,6 +479,7 @@ symbols! { assert_zero_valid, asserting, associated_const_equality, + associated_const_underscore, associated_consts, associated_type_bounds, associated_type_defaults, diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 38bfeb04fbce4..c171b9894151c 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -361,7 +361,10 @@ pub fn dyn_compatibility_violations_for_assoc_item( let span = || item.ident(tcx).span; match item.kind { - ty::AssocKind::Const { name } => { + ty::AssocKind::Const { data: ty::AssocConstData::Anonymous } => { + panic!("anonymous associated consts shouldn't end up here") + } + ty::AssocKind::Const { data: ty::AssocConstData::Named(name) } => { // We will permit type associated consts if they are explicitly mentioned in the // trait object type. We can't check this here, as here we only check if it is // guaranteed to not be possible. diff --git a/compiler/rustc_ty_utils/src/assoc.rs b/compiler/rustc_ty_utils/src/assoc.rs index 6b30019127f27..526419215334b 100644 --- a/compiler/rustc_ty_utils/src/assoc.rs +++ b/compiler/rustc_ty_utils/src/assoc.rs @@ -93,7 +93,9 @@ fn associated_item_from_trait_item( let owner_id = trait_item.owner_id; let name = trait_item.ident.name; let kind = match trait_item.kind { - hir::TraitItemKind::Const(_, _) => ty::AssocKind::Const { name }, + hir::TraitItemKind::Const(_, _) => { + ty::AssocKind::Const { data: ty::AssocConstData::Named(name) } + } hir::TraitItemKind::Fn { .. } => ty::AssocKind::Fn { name, has_self: fn_has_self_parameter(tcx, owner_id, trait_item.generics), @@ -110,7 +112,14 @@ fn associated_item_from_impl_item(tcx: TyCtxt<'_>, impl_item: &hir::ImplItem<'_> let owner_id = impl_item.owner_id; let name = impl_item.ident.name; let kind = match impl_item.kind { - hir::ImplItemKind::Const(..) => ty::AssocKind::Const { name }, + hir::ImplItemKind::Const(..) => { + let data = if impl_item.is_anon_const() { + ty::AssocConstData::Anonymous + } else { + ty::AssocConstData::Named(name) + }; + ty::AssocKind::Const { data } + } hir::ImplItemKind::Fn(..) => ty::AssocKind::Fn { name, has_self: fn_has_self_parameter(tcx, owner_id, impl_item.generics), diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 3bf73c67bc0c1..12056465a033c 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -539,6 +539,7 @@ pub(crate) fn build_impl( .items .iter() .map(|&item| tcx.hir_impl_item(item)) + .filter(|item| !item.is_anon_const()) .filter(|item| { // Filter out impl items whose corresponding trait item has `doc(hidden)` // not to document such impl items. diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index b8e9d8a3f4633..dc8b15a360f59 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -3059,7 +3059,9 @@ fn clean_impl<'tcx>( let items = impl_ .items .iter() - .map(|&ii| clean_impl_item(tcx.hir_impl_item(ii), cx)) + .map(|&ii| tcx.hir_impl_item(ii)) + .filter(|item| !item.is_anon_const()) + .map(|item| clean_impl_item(item, cx)) .collect::>(); // If this impl block is a positive implementation of the Deref trait, then we diff --git a/src/tools/clippy/clippy_lints/src/assigning_clones.rs b/src/tools/clippy/clippy_lints/src/assigning_clones.rs index 290738514b69d..f9e00d999137b 100644 --- a/src/tools/clippy/clippy_lints/src/assigning_clones.rs +++ b/src/tools/clippy/clippy_lints/src/assigning_clones.rs @@ -108,7 +108,7 @@ impl<'tcx> LateLintPass<'tcx> for AssigningClones { }) && let resolved_assoc_items = cx.tcx.associated_items(resolved_impl) // Only suggest if `clone_from`/`clone_into` is explicitly implemented - && resolved_assoc_items.in_definition_order().any(|assoc| + && resolved_assoc_items.named_items_in_definition_order().any(|assoc| match which_trait { CloneTrait::Clone => assoc.name() == sym::clone_from, CloneTrait::ToOwned => assoc.name() == sym::clone_into, diff --git a/tests/rustdoc-html/associated-const-underscore.rs b/tests/rustdoc-html/associated-const-underscore.rs new file mode 100644 index 0000000000000..ab2b3bc6cf1d3 --- /dev/null +++ b/tests/rustdoc-html/associated-const-underscore.rs @@ -0,0 +1,11 @@ +#![feature(associated_const_underscore)] + +pub struct Struct; + +impl Struct { + //@ has associated_const_underscore/struct.Struct.html '//*[@id="associatedconstant.NAMED"]' '' + pub const NAMED: () = (); + + //@ !has associated_const_underscore/struct.Struct.html '//*[@id="associatedconstant._"]' '' + pub const _: () = (); +} diff --git a/tests/rustdoc-json/associated-const-underscore.rs b/tests/rustdoc-json/associated-const-underscore.rs new file mode 100644 index 0000000000000..8626dabaacfdd --- /dev/null +++ b/tests/rustdoc-json/associated-const-underscore.rs @@ -0,0 +1,11 @@ +#![feature(associated_const_underscore)] + +pub struct Struct; + +impl Struct { + //@ has "$..index[?(@.name=='NAMED')].inner.assoc_const" + pub const NAMED: () = (); + + //@ !has "$..index[?(@.name=='_')]" + pub const _: () = (); +} diff --git a/tests/ui/associated-consts/associated-const-underscore-dead-code.rs b/tests/ui/associated-consts/associated-const-underscore-dead-code.rs new file mode 100644 index 0000000000000..866d67eee6f3b --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-dead-code.rs @@ -0,0 +1,13 @@ +#![feature(associated_const_underscore)] +#![deny(dead_code)] + +fn main() {} + +struct Struct {} + +impl Struct { + const _: () = { + struct Unused; + //~^ ERROR: struct `Unused` is never constructed [dead_code] + }; +} diff --git a/tests/ui/associated-consts/associated-const-underscore-dead-code.stderr b/tests/ui/associated-consts/associated-const-underscore-dead-code.stderr new file mode 100644 index 0000000000000..246b145076f13 --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-dead-code.stderr @@ -0,0 +1,14 @@ +error: struct `Unused` is never constructed + --> $DIR/associated-const-underscore-dead-code.rs:10:16 + | +LL | struct Unused; + | ^^^^^^ + | +note: the lint level is defined here + --> $DIR/associated-const-underscore-dead-code.rs:2:9 + | +LL | #![deny(dead_code)] + | ^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/associated-consts/associated-const-underscore-fail.rs b/tests/ui/associated-consts/associated-const-underscore-fail.rs new file mode 100644 index 0000000000000..f1fcdab96b137 --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-fail.rs @@ -0,0 +1,41 @@ +#![feature(associated_const_underscore)] + +fn main() {} + +pub struct Struct; + +impl Struct { + const _: () = { + let _ = Struct; + }; + // typeck + const _: u32 = "not a number"; + //~^ ERROR mismatched types +} + +trait InvalidTrait { + const _: (); + //~^ ERROR `const` items in this context need a name +} + +trait Trait {} + +struct Type; + +impl Trait for Type { + const _: () = (); + //~^ ERROR `const` items in this context need a name + //~| ERROR const `_` is not a member of trait `Trait` +} + +struct Local; + +impl std::thread::Thread { + //~^ ERROR cannot define inherent `impl` for a type outside of the crate where the type is defined + const _: () = (); +} + +impl &Local { + //~^ ERROR cannot define inherent `impl` for primitive types + const _: () = (); +} diff --git a/tests/ui/associated-consts/associated-const-underscore-fail.stderr b/tests/ui/associated-consts/associated-const-underscore-fail.stderr new file mode 100644 index 0000000000000..a56b3415bd7be --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-fail.stderr @@ -0,0 +1,48 @@ +error: `const` items in this context need a name + --> $DIR/associated-const-underscore-fail.rs:17:11 + | +LL | const _: (); + | ^ `_` is not a valid name for this `const` item + +error: `const` items in this context need a name + --> $DIR/associated-const-underscore-fail.rs:26:11 + | +LL | const _: () = (); + | ^ `_` is not a valid name for this `const` item + +error[E0438]: const `_` is not a member of trait `Trait` + --> $DIR/associated-const-underscore-fail.rs:26:5 + | +LL | const _: () = (); + | ^^^^^^^^^^^^^^^^^ not a member of trait `Trait` + +error[E0116]: cannot define inherent `impl` for a type outside of the crate where the type is defined + --> $DIR/associated-const-underscore-fail.rs:33:1 + | +LL | impl std::thread::Thread { + | ^^^^^^^^^^^^^^^^^^^^^^^^ impl for type defined outside of crate + | + = help: consider defining a trait and implementing it for the type or using a newtype wrapper like `struct MyType(ExternalType);` and implement it + = note: for more details about the orphan rules, see + +error[E0390]: cannot define inherent `impl` for primitive types + --> $DIR/associated-const-underscore-fail.rs:38:1 + | +LL | impl &Local { + | ^^^^^^^^^^^ + | + = help: consider using an extension trait instead + = note: you could also try moving the reference to uses of `Local` (such as `self`) within the implementation + +error[E0308]: mismatched types + --> $DIR/associated-const-underscore-fail.rs:12:20 + | +LL | const _: u32 = "not a number"; + | --- ^^^^^^^^^^^^^^ expected `u32`, found `&str` + | | + | expected because of the type of the associated constant + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0116, E0308, E0390, E0438. +For more information about an error, try `rustc --explain E0116`. diff --git a/tests/ui/associated-consts/associated-const-underscore-visibility.rs b/tests/ui/associated-consts/associated-const-underscore-visibility.rs new file mode 100644 index 0000000000000..52fefed006e27 --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-visibility.rs @@ -0,0 +1,20 @@ +#![feature(associated_const_underscore)] +#![deny(private_interfaces)] +fn main() {} + +pub struct Public; +struct Private; + +impl Public { + pub const _: Private = Private; + //~^ ERROR: type `Private` is more private than the item `Public::_` [private_interfaces] +} + +impl Public { + const _: Private = Private; +} + +impl Private { + pub const _: Private = Private; + pub const _: () = {}; +} diff --git a/tests/ui/associated-consts/associated-const-underscore-visibility.stderr b/tests/ui/associated-consts/associated-const-underscore-visibility.stderr new file mode 100644 index 0000000000000..9a055038ad52c --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore-visibility.stderr @@ -0,0 +1,19 @@ +error: type `Private` is more private than the item `Public::_` + --> $DIR/associated-const-underscore-visibility.rs:9:5 + | +LL | pub const _: Private = Private; + | ^^^^^^^^^^^^^^^^^^^^ associated constant `Public::_` is reachable at visibility `pub` + | +note: but type `Private` is only usable at visibility `pub(crate)` + --> $DIR/associated-const-underscore-visibility.rs:6:1 + | +LL | struct Private; + | ^^^^^^^^^^^^^^ +note: the lint level is defined here + --> $DIR/associated-const-underscore-visibility.rs:2:9 + | +LL | #![deny(private_interfaces)] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/associated-consts/associated-const-underscore.rs b/tests/ui/associated-consts/associated-const-underscore.rs new file mode 100644 index 0000000000000..d1a79c5636663 --- /dev/null +++ b/tests/ui/associated-consts/associated-const-underscore.rs @@ -0,0 +1,75 @@ +//@ check-pass + +#![feature(associated_const_underscore)] + +use std::mem; + +struct Struct(T); + +impl Struct { + const _: () = { + let _: Option; + }; + + const _: i16 = 0; +} + +impl Struct { + const _: () = (); +} + +type Field<'a, T> = &'a mut T; + +struct Thing<'a, T> { + field: Field<'a, T>, +} + +impl<'a, T: Eq> Thing<'a, T> { + const _: () = { + fn require_outlives<'a, T: 'a>() {} + let _ = require_outlives::<'a, T>; + let _: Option; + }; +} + +struct Unit; + +impl Unit { + // An associated const named `_` is not evaluated unless it is accessed. + // However, associated consts named `_` cannot be accessed, so this should pass. + const _: () = panic!(); + const _: () = assert!(false); + const _: [(); { + { + assert!(std::mem::size_of::() == 0) + }; + 0 + }] = []; + const _: [(); { + { + assert!(true) + }; + 0 + }] = []; +} + +struct Generic(T); + +impl Generic { + const _: () = assert!(mem::size_of::() % 2 == 0); +} + +fn main() { + let _ = Unit; + let _ = Generic([0u8; 3]); +} + +trait CfgTrait { + #[cfg(any())] + const _: () = (); +} + +impl CfgTrait for Unit { + #[cfg(any())] + const _: () = (); +} diff --git a/tests/ui/feature-gates/feature-gate-associated-const-underscore.rs b/tests/ui/feature-gates/feature-gate-associated-const-underscore.rs new file mode 100644 index 0000000000000..f3a8cc7179c2d --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-associated-const-underscore.rs @@ -0,0 +1,10 @@ +struct Thing {} + +impl Thing { + const _: () = {}; + //~^ ERROR: naming associated constants with `_` is unstable [E0658] +} + +fn main() { + let _ = Thing {}; +} diff --git a/tests/ui/feature-gates/feature-gate-associated-const-underscore.stderr b/tests/ui/feature-gates/feature-gate-associated-const-underscore.stderr new file mode 100644 index 0000000000000..665ba9cb9dbcd --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-associated-const-underscore.stderr @@ -0,0 +1,13 @@ +error[E0658]: naming associated constants with `_` is unstable + --> $DIR/feature-gate-associated-const-underscore.rs:4:11 + | +LL | const _: () = {}; + | ^ + | + = note: see issue #158944 for more information + = help: add `#![feature(associated_const_underscore)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.rs b/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.rs index d37ce06c555bf..f266e3a7cd837 100644 --- a/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.rs +++ b/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.rs @@ -1,5 +1,4 @@ -// Semantically, an associated constant cannot use `_` as a name. - +// Semantically, a trait/trait impl associated constant cannot use _ as a name. fn main() {} const _: () = { @@ -10,8 +9,4 @@ const _: () = { const _: () = (); //~ ERROR `const` items in this context need a name //~^ ERROR const `_` is not a member of trait `A` } - struct B; - impl B { - const _: () = (); //~ ERROR `const` items in this context need a name - } }; diff --git a/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.stderr b/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.stderr index 538bf0ec100dc..6d7043abff802 100644 --- a/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.stderr +++ b/tests/ui/parser/assoc/assoc-const-underscore-semantic-fail.stderr @@ -1,27 +1,21 @@ error: `const` items in this context need a name - --> $DIR/assoc-const-underscore-semantic-fail.rs:7:15 + --> $DIR/assoc-const-underscore-semantic-fail.rs:6:15 | LL | const _: () = (); | ^ `_` is not a valid name for this `const` item error: `const` items in this context need a name - --> $DIR/assoc-const-underscore-semantic-fail.rs:10:15 - | -LL | const _: () = (); - | ^ `_` is not a valid name for this `const` item - -error: `const` items in this context need a name - --> $DIR/assoc-const-underscore-semantic-fail.rs:15:15 + --> $DIR/assoc-const-underscore-semantic-fail.rs:9:15 | LL | const _: () = (); | ^ `_` is not a valid name for this `const` item error[E0438]: const `_` is not a member of trait `A` - --> $DIR/assoc-const-underscore-semantic-fail.rs:10:9 + --> $DIR/assoc-const-underscore-semantic-fail.rs:9:9 | LL | const _: () = (); | ^^^^^^^^^^^^^^^^^ not a member of trait `A` -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0438`. From b6f220a93631107de1688f077f4cd924882a8328 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Mon, 9 Nov 2026 23:32:20 +0200 Subject: [PATCH 08/12] Properly clear search state when sidebar link is clicked --- src/librustdoc/html/static/js/main.js | 7 +------ .../rustdoc-gui/search-button-sidebar-anchor.goml | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 tests/rustdoc-gui/search-button-sidebar-anchor.goml diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 11bf38b1e4084..a7d0b0251310b 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -546,13 +546,8 @@ function preLoadCss(cssUrl) { if (ev !== null && window.searchState.isDisplayed() && ev.newURL) { // This block occurs when clicking on an element in the navbar while // in a search. - switchDisplayedElement(null); + window.searchState.hideResults(); const hash = ev.newURL.slice(ev.newURL.indexOf("#") + 1); - if (browserSupportsHistoryApi()) { - // `window.location.search`` contains all the query parameters, not just `search`. - history.replaceState(null, "", - getNakedUrl() + window.location.search + "#" + hash); - } const elem = document.getElementById(hash); if (elem) { elem.scrollIntoView(); diff --git a/tests/rustdoc-gui/search-button-sidebar-anchor.goml b/tests/rustdoc-gui/search-button-sidebar-anchor.goml new file mode 100644 index 0000000000000..7cad070fe6086 --- /dev/null +++ b/tests/rustdoc-gui/search-button-sidebar-anchor.goml @@ -0,0 +1,15 @@ +// This test ensures that clicking on a sidebar anchor while the search is open closes the +// search, resets the search button and removes the search query from the URL. +include: "utils.goml" + +go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html" + +call-function: ("open-search", {}) +assert-text: ("#search-button a span.label", "Exit") +assert-document-property: ({"URL": "struct.Foo.html?search="}, CONTAINS) + +click: "//*[@class='sidebar']//a[@href='#method.must_use']" +wait-for: "#not-displayed #search" +assert-attribute: ("#main-content", {"class": "content"}) +assert-text: ("#search-button a span.label", "Search") +assert-document-property: ({"URL": "struct.Foo.html#method.must_use"}, ENDS_WITH) From df9f64648d01cbb5ae493f26a3440cb94822540b Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 10 Nov 2026 13:48:34 +0100 Subject: [PATCH 09/12] rustdoc: Rename `--show-coverage` CLI flag into `--coverage` --- src/doc/rustc-dev-guide/src/rustdoc-internals.md | 2 +- src/doc/rustdoc/src/unstable-features.md | 10 +++++----- src/librustdoc/calculate_doc_coverage.rs | 2 +- src/librustdoc/config.rs | 14 +++++++------- src/librustdoc/core.rs | 2 +- src/librustdoc/lib.rs | 2 +- tests/run-make/rustdoc-show-coverage/rmake.rs | 4 ++-- .../rustdoc/default-output/output-default.stdout | 3 +-- tests/rustdoc-ui/coverage/allow_missing_docs.rs | 2 +- tests/rustdoc-ui/coverage/basic.rs | 2 +- tests/rustdoc-ui/coverage/doc-examples-json.rs | 2 +- tests/rustdoc-ui/coverage/doc-examples.rs | 2 +- tests/rustdoc-ui/coverage/doctest-output.rs | 4 ++-- tests/rustdoc-ui/coverage/doctest-output.stderr | 2 +- tests/rustdoc-ui/coverage/empty.rs | 2 +- tests/rustdoc-ui/coverage/enum-tuple-documented.rs | 2 +- tests/rustdoc-ui/coverage/enum-tuple.rs | 2 +- tests/rustdoc-ui/coverage/enums.rs | 2 +- tests/rustdoc-ui/coverage/exotic.rs | 2 +- tests/rustdoc-ui/coverage/html.rs | 4 ++-- tests/rustdoc-ui/coverage/html.stderr | 2 +- tests/rustdoc-ui/coverage/json.rs | 2 +- .../output-format-coveragejson-emit-depinfo.rs | 2 +- .../coverage/output-format-json-emit-html.rs | 4 ++-- tests/rustdoc-ui/coverage/private.rs | 2 +- .../show-coverage-json-emit-html-non-static.rs | 2 +- tests/rustdoc-ui/coverage/show-coverage-json.rs | 2 +- tests/rustdoc-ui/coverage/show-coverage.rs | 2 +- tests/rustdoc-ui/coverage/statics-consts.rs | 2 +- tests/rustdoc-ui/coverage/traits.rs | 2 +- 30 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/doc/rustc-dev-guide/src/rustdoc-internals.md b/src/doc/rustc-dev-guide/src/rustdoc-internals.md index df8adab351e65..21957694d85dc 100644 --- a/src/doc/rustc-dev-guide/src/rustdoc-internals.md +++ b/src/doc/rustc-dev-guide/src/rustdoc-internals.md @@ -105,7 +105,7 @@ transformations, the passes are run on the _cleaned_ crate. Here is the list of passes as of March 2023: -- `calculate-doc-coverage` calculates information used for the `--show-coverage` +- `calculate-doc-coverage` calculates information used for the `--coverage` flag. - `check-doc-test-visibility` runs `doctest` visibility–related `lint`s. diff --git a/src/doc/rustdoc/src/unstable-features.md b/src/doc/rustdoc/src/unstable-features.md index 9bd795b759ec4..a699013ca1bc5 100644 --- a/src/doc/rustdoc/src/unstable-features.md +++ b/src/doc/rustdoc/src/unstable-features.md @@ -485,14 +485,14 @@ This flag allows you to keep doctest executables around after they're compiled o Usually, rustdoc will immediately discard a compiled doctest after it's been tested, but with this option, you can keep those binaries around for farther testing. -## `--show-coverage`: calculate the percentage of items with documentation +## `--coverage`: calculate the percentage of items with documentation * Tracking issue: [#58154](https://github.com/rust-lang/rust/issues/58154) Using this flag looks like this: ```bash -$ rustdoc src/lib.rs -Z unstable-options --show-coverage +$ rustdoc src/lib.rs -Z unstable-options --coverage ``` It generates something like this: @@ -539,7 +539,7 @@ Calculating code examples follows these rules: If you use the `-o` option with it, it will generate the file into the given older name. For example: ```shell -rustdoc foo.rs --show-coverage -o doc +rustdoc foo.rs --coverage -o doc ``` Will generate a `foo.txt` into the `doc` folder. If the `-o` option isn't passed, it will display @@ -586,8 +586,8 @@ rustup component add --toolchain nightly rust-docs-json Then the json files will be present in the `share/doc/rust/json/` directory of the rustup toolchain directory. -It can also be used with `--show-coverage`. Take a look at its -[documentation](#--show-coverage-calculate-the-percentage-of-items-with-documentation) for more +It can also be used with `--coverage`. Take a look at its +[documentation](#--coverage-calculate-the-percentage-of-items-with-documentation) for more information. ### doctest diff --git a/src/librustdoc/calculate_doc_coverage.rs b/src/librustdoc/calculate_doc_coverage.rs index 45e1c18051444..dc00a689d7bc9 100644 --- a/src/librustdoc/calculate_doc_coverage.rs +++ b/src/librustdoc/calculate_doc_coverage.rs @@ -1,4 +1,4 @@ -//! Calculates information used for the `--show-coverage` flag. +//! Calculates information used for the `--coverage` flag. //! //! More specifically, it counts the number of items with documentation, ones with //! "examples" (i.e., non-ignored Rust code blocks) and various totals. diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 9acf150b3b354..4c8793c2cd4a6 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -32,11 +32,11 @@ use crate::{html, opts, theme}; #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub(crate) enum OutputFormat { - /// `--output-format=json` without `--show-coverage`. + /// `--output-format=json` without `--coverage`. /// /// JSON description of crate API. IrJson, - /// `--output-format=json` with `--show-coverage`. + /// `--output-format=json` with `--coverage`. CoverageJson, Html, Doctest, @@ -151,7 +151,7 @@ pub(crate) struct Options { pub(crate) crate_version: Option, /// The format that we output when rendering. /// - /// Currently used only for the `--show-coverage` option. + /// Currently used only for the `--coverage` option. pub(crate) output_format: OutputFormat, /// If this option is set to `true`, rustdoc will only run checks and not generate /// documentation. @@ -437,7 +437,7 @@ impl Options { check_deprecated_options(matches, dcx); let should_test = matches.opt_present("test"); - let show_coverage = matches.opt_present("show-coverage"); + let show_coverage = matches.opt_present("coverage"); let output_format_s = matches.opt_str("output-format"); let output_format = match output_format_s.as_deref() { None | Some("html") => OutputFormat::Html, @@ -452,7 +452,7 @@ impl Options { Some(other) => dcx.fatal(format!("unknown output format `{other}`")), }; - // check for `--output-format` stability, and compatibility with `--show-coverage` + // check for `--output-format` stability, and compatibility with `--coverage` match ( output_format_s.as_ref().map(|_| output_format), show_coverage, @@ -461,7 +461,7 @@ impl Options { (None | Some(OutputFormat::CoverageJson), true, _) => {} (_, true, _) => { dcx.fatal(format!( - "`--output-format={}` is not supported for the `--show-coverage` option", + "`--output-format={}` is not supported for the `--coverage` option", output_format_s.expect("checked for none above"), )); } @@ -715,7 +715,7 @@ impl Options { } (None, None) => { if show_coverage { - // If no `-o` option is given and we're in the `--show-coverage` mode, by + // If no `-o` option is given and we're in the `--coverage` mode, by // default we print on the stdout. output_to_stdout = true; } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 0c3995f1d3620..ff60e7dd5b00f 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -144,7 +144,7 @@ impl<'tcx> DocContext<'tcx> { /// Returns `true` if the JSON output format is enabled for generating the crate content. /// - /// If another option like `--show-coverage` is enabled, it will return `false`. + /// If another option like `--coverage` is enabled, it will return `false`. pub(crate) fn is_json_output(&self) -> bool { self.output_format == OutputFormat::IrJson } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 4f974b35cf0ba..9a16942a2e5bc 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -499,7 +499,7 @@ fn opts() -> Vec { Unstable, FlagMulti, "", - "show-coverage", + "coverage", "calculate percentage of public items with documentation", "", ), diff --git a/tests/run-make/rustdoc-show-coverage/rmake.rs b/tests/run-make/rustdoc-show-coverage/rmake.rs index 90ad50267b8ad..43cfdcb26976a 100644 --- a/tests/run-make/rustdoc-show-coverage/rmake.rs +++ b/tests/run-make/rustdoc-show-coverage/rmake.rs @@ -1,4 +1,4 @@ -// This test ensures that `-o` option works as expected with `--show-coverage`. +// This test ensures that `-o` option works as expected with `--coverage`. // Regression test for . //@ needs-target-std @@ -12,7 +12,7 @@ fn run_rustdoc(extra_args: &[&str]) -> String { rustdoc() .input("foo.rs") .arg("-Zunstable-options") - .arg("--show-coverage") + .arg("--coverage") .args(extra_args) .run() .stdout_utf8() diff --git a/tests/run-make/rustdoc/default-output/output-default.stdout b/tests/run-make/rustdoc/default-output/output-default.stdout index 346140b2d921c..7178f53cb2120 100644 --- a/tests/run-make/rustdoc/default-output/output-default.stdout +++ b/tests/run-make/rustdoc/default-output/output-default.stdout @@ -140,8 +140,7 @@ Options: to reach the documentation root. --persist-doctests PATH Directory to persist doctest executables into - --show-coverage - calculate percentage of public items with + --coverage calculate percentage of public items with documentation --test-runtool The tool to run tests with when building for a different target than host diff --git a/tests/rustdoc-ui/coverage/allow_missing_docs.rs b/tests/rustdoc-ui/coverage/allow_missing_docs.rs index feca5a1bfe3e9..86a4f211fb6b9 100644 --- a/tests/rustdoc-ui/coverage/allow_missing_docs.rs +++ b/tests/rustdoc-ui/coverage/allow_missing_docs.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass //! Make sure to have some docs on your crate root diff --git a/tests/rustdoc-ui/coverage/basic.rs b/tests/rustdoc-ui/coverage/basic.rs index fc44f6826cb9c..99c2305baf95c 100644 --- a/tests/rustdoc-ui/coverage/basic.rs +++ b/tests/rustdoc-ui/coverage/basic.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass #![feature(extern_types)] diff --git a/tests/rustdoc-ui/coverage/doc-examples-json.rs b/tests/rustdoc-ui/coverage/doc-examples-json.rs index bfec2600cb875..8aa5c5c4c1267 100644 --- a/tests/rustdoc-ui/coverage/doc-examples-json.rs +++ b/tests/rustdoc-ui/coverage/doc-examples-json.rs @@ -1,5 +1,5 @@ //@ check-pass -//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o - +//@ compile-flags:-Z unstable-options --output-format json --coverage -o - // This check ensures that only one doc example is counted since they're "optional" on // certain items. diff --git a/tests/rustdoc-ui/coverage/doc-examples.rs b/tests/rustdoc-ui/coverage/doc-examples.rs index dd8969f934ee2..b0ce9bd5e50b9 100644 --- a/tests/rustdoc-ui/coverage/doc-examples.rs +++ b/tests/rustdoc-ui/coverage/doc-examples.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass //! This test ensure that only rust code examples are counted. diff --git a/tests/rustdoc-ui/coverage/doctest-output.rs b/tests/rustdoc-ui/coverage/doctest-output.rs index 28364c3a3ea5e..2fcbf5ed408a1 100644 --- a/tests/rustdoc-ui/coverage/doctest-output.rs +++ b/tests/rustdoc-ui/coverage/doctest-output.rs @@ -1,3 +1,3 @@ -//@ compile-flags:-Z unstable-options --show-coverage --output-format=doctest +//@ compile-flags:-Z unstable-options --coverage --output-format=doctest -//~? ERROR `--output-format=doctest` is not supported for the `--show-coverage` option +//~? ERROR `--output-format=doctest` is not supported for the `--coverage` option diff --git a/tests/rustdoc-ui/coverage/doctest-output.stderr b/tests/rustdoc-ui/coverage/doctest-output.stderr index 20c618dc61b16..8f6deeb9fa213 100644 --- a/tests/rustdoc-ui/coverage/doctest-output.stderr +++ b/tests/rustdoc-ui/coverage/doctest-output.stderr @@ -1,2 +1,2 @@ -error: `--output-format=doctest` is not supported for the `--show-coverage` option +error: `--output-format=doctest` is not supported for the `--coverage` option diff --git a/tests/rustdoc-ui/coverage/empty.rs b/tests/rustdoc-ui/coverage/empty.rs index fd9f1e617fbec..f2c6ceca8edcb 100644 --- a/tests/rustdoc-ui/coverage/empty.rs +++ b/tests/rustdoc-ui/coverage/empty.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass // an empty crate still has one item to document: the crate root diff --git a/tests/rustdoc-ui/coverage/enum-tuple-documented.rs b/tests/rustdoc-ui/coverage/enum-tuple-documented.rs index 9e88e896f8da7..0972610b09e32 100644 --- a/tests/rustdoc-ui/coverage/enum-tuple-documented.rs +++ b/tests/rustdoc-ui/coverage/enum-tuple-documented.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass // The point of this test is to ensure that the number of "documented" items diff --git a/tests/rustdoc-ui/coverage/enum-tuple.rs b/tests/rustdoc-ui/coverage/enum-tuple.rs index 25d3c9f57bafe..6a4b2bdbf6db1 100644 --- a/tests/rustdoc-ui/coverage/enum-tuple.rs +++ b/tests/rustdoc-ui/coverage/enum-tuple.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass //! (remember the crate root is still a module) diff --git a/tests/rustdoc-ui/coverage/enums.rs b/tests/rustdoc-ui/coverage/enums.rs index bdb213c2a53b7..5db9e97c8fe52 100644 --- a/tests/rustdoc-ui/coverage/enums.rs +++ b/tests/rustdoc-ui/coverage/enums.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass //! (remember the crate root is still a module) diff --git a/tests/rustdoc-ui/coverage/exotic.rs b/tests/rustdoc-ui/coverage/exotic.rs index 947ff43713b44..b84a52df921d9 100644 --- a/tests/rustdoc-ui/coverage/exotic.rs +++ b/tests/rustdoc-ui/coverage/exotic.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass #![feature(rustdoc_internals)] diff --git a/tests/rustdoc-ui/coverage/html.rs b/tests/rustdoc-ui/coverage/html.rs index 7fd080f075824..402d5b3836fa4 100644 --- a/tests/rustdoc-ui/coverage/html.rs +++ b/tests/rustdoc-ui/coverage/html.rs @@ -1,6 +1,6 @@ -//@ compile-flags:-Z unstable-options --output-format html --show-coverage +//@ compile-flags:-Z unstable-options --output-format html --coverage /// Foo pub struct Xo; -//~? ERROR `--output-format=html` is not supported for the `--show-coverage` option +//~? ERROR `--output-format=html` is not supported for the `--coverage` option diff --git a/tests/rustdoc-ui/coverage/html.stderr b/tests/rustdoc-ui/coverage/html.stderr index 764179820c5ba..457163ea2e4a0 100644 --- a/tests/rustdoc-ui/coverage/html.stderr +++ b/tests/rustdoc-ui/coverage/html.stderr @@ -1,2 +1,2 @@ -error: `--output-format=html` is not supported for the `--show-coverage` option +error: `--output-format=html` is not supported for the `--coverage` option diff --git a/tests/rustdoc-ui/coverage/json.rs b/tests/rustdoc-ui/coverage/json.rs index dcab7df252249..cbedebe308389 100644 --- a/tests/rustdoc-ui/coverage/json.rs +++ b/tests/rustdoc-ui/coverage/json.rs @@ -1,5 +1,5 @@ //@ check-pass -//@ compile-flags:-Z unstable-options --output-format json --show-coverage -o - +//@ compile-flags:-Z unstable-options --output-format json --coverage -o - pub mod foo { /// Hello! diff --git a/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs b/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs index 97cdedecb0aaf..4c1c36b93b82a 100644 --- a/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs +++ b/tests/rustdoc-ui/coverage/output-format-coveragejson-emit-depinfo.rs @@ -1,2 +1,2 @@ -//@ compile-flags: --show-coverage --output-format=json --emit=dep-info -Zunstable-options -o - +//@ compile-flags: --coverage --output-format=json --emit=dep-info -Zunstable-options -o - //@ build-pass diff --git a/tests/rustdoc-ui/coverage/output-format-json-emit-html.rs b/tests/rustdoc-ui/coverage/output-format-json-emit-html.rs index f97392bab5b68..9bf47d11c8008 100644 --- a/tests/rustdoc-ui/coverage/output-format-json-emit-html.rs +++ b/tests/rustdoc-ui/coverage/output-format-json-emit-html.rs @@ -1,9 +1,9 @@ //@ revisions: html_static html_non_static html_static_coverage html_non_static_coverage //@ compile-flags: -Zunstable-options --output-format json //@[html_static] compile-flags: --emit html-static-files -//@[html_static_coverage] compile-flags: --emit html-static-files --show-coverage +//@[html_static_coverage] compile-flags: --emit html-static-files --coverage //@[html_non_static] compile-flags: --emit html-non-static-files -//@[html_non_static_coverage] compile-flags: --emit html-non-static-files --show-coverage +//@[html_non_static_coverage] compile-flags: --emit html-non-static-files --coverage //[html_static]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json` //[html_static_coverage]~? ERROR the `--emit=html-static-files` flag is not supported with `--output-format=json` diff --git a/tests/rustdoc-ui/coverage/private.rs b/tests/rustdoc-ui/coverage/private.rs index 3b78c5d761dbc..8cb59d962b2be 100644 --- a/tests/rustdoc-ui/coverage/private.rs +++ b/tests/rustdoc-ui/coverage/private.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage --document-private-items -o - +//@ compile-flags:-Z unstable-options --coverage --document-private-items -o - //@ check-pass #![allow(unused)] diff --git a/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs b/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs index b0f67a3b4195e..225eba039f30f 100644 --- a/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs +++ b/tests/rustdoc-ui/coverage/show-coverage-json-emit-html-non-static.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z unstable-options --show-coverage --output-format=json --emit=html-non-static-files +//@ compile-flags: -Z unstable-options --coverage --output-format=json --emit=html-non-static-files //@ check-fail //~? ERROR the `--emit=html-non-static-files` flag is not supported with `--output-format=json` diff --git a/tests/rustdoc-ui/coverage/show-coverage-json.rs b/tests/rustdoc-ui/coverage/show-coverage-json.rs index e7ddc69d79f8a..5929d6fa105c0 100644 --- a/tests/rustdoc-ui/coverage/show-coverage-json.rs +++ b/tests/rustdoc-ui/coverage/show-coverage-json.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z unstable-options --show-coverage --output-format=json -o - +//@ compile-flags: -Z unstable-options --coverage --output-format=json -o - //@ check-pass mod bar { diff --git a/tests/rustdoc-ui/coverage/show-coverage.rs b/tests/rustdoc-ui/coverage/show-coverage.rs index 1f9c3a8f805b7..8995974566c4b 100644 --- a/tests/rustdoc-ui/coverage/show-coverage.rs +++ b/tests/rustdoc-ui/coverage/show-coverage.rs @@ -1,4 +1,4 @@ -//@ compile-flags: -Z unstable-options --show-coverage -o - +//@ compile-flags: -Z unstable-options --coverage -o - //@ check-pass mod bar { diff --git a/tests/rustdoc-ui/coverage/statics-consts.rs b/tests/rustdoc-ui/coverage/statics-consts.rs index 5177673f7958a..75e88cc64bcbd 100644 --- a/tests/rustdoc-ui/coverage/statics-consts.rs +++ b/tests/rustdoc-ui/coverage/statics-consts.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass //! gotta make sure we can count statics and consts correctly, too diff --git a/tests/rustdoc-ui/coverage/traits.rs b/tests/rustdoc-ui/coverage/traits.rs index 37e147004d714..fd57fc8ff3b43 100644 --- a/tests/rustdoc-ui/coverage/traits.rs +++ b/tests/rustdoc-ui/coverage/traits.rs @@ -1,4 +1,4 @@ -//@ compile-flags:-Z unstable-options --show-coverage -o - +//@ compile-flags:-Z unstable-options --coverage -o - //@ check-pass #![feature(trait_alias)] From 3e405fc18468ee11d0f5d37d2b8df3bcf3a4ce66 Mon Sep 17 00:00:00 2001 From: Adam Gemmell Date: Tue, 10 Nov 2026 15:00:48 +0000 Subject: [PATCH 10/12] Use dist profile when benching std --- src/bootstrap/src/core/builder/cargo.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 27de3ec60b96b..5928860bad0a9 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -1505,18 +1505,14 @@ impl Builder<'_> { .unwrap_or(&self.config.rust_rustflags) .clone(); - let profile = - if matches!(cmd_kind, Kind::Bench | Kind::Miri | Kind::MiriSetup | Kind::MiriTest) { - // Use the default profile for bench/miri - None - } else { - match (mode, self.config.rust_optimize.is_release()) { - // Some std configuration exists in its own profile - (Mode::Std, _) => Some("dist"), - (_, true) => Some("release"), - (_, false) => Some("dev"), - } - }; + let profile = match (cmd_kind, mode) { + // Use the default profile for miri + (Kind::Miri | Kind::MiriSetup | Kind::MiriTest, _) => None, + // Some std configuration exists in its own profile + (_, Mode::Std) => Some("dist"), + _ if self.config.rust_optimize.is_release() => Some("release"), + _ => Some("dev"), + }; Cargo { command: cargo, From f2a332d7b80cfe06ac53fc63c6e7006396273298 Mon Sep 17 00:00:00 2001 From: panstromek Date: Tue, 10 Nov 2026 11:50:28 +0100 Subject: [PATCH 11/12] Extract assert_terminator* functions to remove duplication at block_data* callsites --- compiler/rustc_mir_build/src/builder/cfg.rs | 12 ++++++++++-- compiler/rustc_mir_build/src/builder/scope.rs | 13 +++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/cfg.rs b/compiler/rustc_mir_build/src/builder/cfg.rs index b9ff4f2e17635..b540f0d8cb718 100644 --- a/compiler/rustc_mir_build/src/builder/cfg.rs +++ b/compiler/rustc_mir_build/src/builder/cfg.rs @@ -11,14 +11,22 @@ use tracing::debug; use crate::builder::{CFG, CFGBlock, UnterminatedBlockData}; impl<'tcx> CFG<'tcx> { - pub(crate) fn block_data(&self, blk: BasicBlock) -> &CFGBlock<'tcx> { + fn block_data(&self, blk: BasicBlock) -> &CFGBlock<'tcx> { &self.basic_blocks[blk] } - pub(crate) fn block_data_mut(&mut self, blk: BasicBlock) -> &mut CFGBlock<'tcx> { + fn block_data_mut(&mut self, blk: BasicBlock) -> &mut CFGBlock<'tcx> { &mut self.basic_blocks[blk] } + pub(crate) fn assert_terminator(&self, bb: BasicBlock) -> &Terminator<'tcx> { + self.block_data(bb).as_ref().left().unwrap().terminator() + } + + pub(crate) fn assert_terminator_mut(&mut self, bb: BasicBlock) -> &mut Terminator<'tcx> { + self.block_data_mut(bb).as_mut().left().unwrap().terminator_mut() + } + pub(crate) fn statements(&self, blk: BasicBlock) -> &[Statement<'tcx>] { match &self.basic_blocks[blk] { CFGBlock::Left(bb) => &bb.statements, diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index 22e3b3e6dfcc4..28b2a98b6120d 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -1621,7 +1621,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn diverge_from(&mut self, start: BasicBlock) { debug_assert!( matches!( - self.cfg.block_data(start).as_ref().left().unwrap().terminator().kind, + self.cfg.assert_terminator(start).kind, TerminatorKind::Assert { .. } | TerminatorKind::Call { .. } | TerminatorKind::Drop { .. } @@ -1680,10 +1680,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// This path terminates in CoroutineDrop. pub(crate) fn coroutine_drop_cleanup(&mut self, yield_block: BasicBlock) { debug_assert!( - matches!( - self.cfg.block_data(yield_block).as_ref().left().unwrap().terminator().kind, - TerminatorKind::Yield { .. } - ), + matches!(self.cfg.assert_terminator(yield_block).kind, TerminatorKind::Yield { .. }), "coroutine_drop_cleanup called on block with non-yield terminator." ); let cached_drop = self.diverge_dropline(); @@ -2108,7 +2105,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for ExitScopes { // There should be an existing terminator with real source info and a // dummy TerminatorKind. Replace it with a proper goto. // (The dummy is added by `break_scope` and `break_from_if_then_scope`.) - let term = cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); + let term = cfg.assert_terminator_mut(from); if let TerminatorKind::UnwindResume = term.kind { term.kind = TerminatorKind::goto(to); } else { @@ -2124,7 +2121,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for CoroutineDrop { cfg.start_new_block() } fn link_entry_point(cfg: &mut CFG<'tcx>, from: BasicBlock, to: BasicBlock) { - let term = cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); + let term = cfg.assert_terminator_mut(from); if let TerminatorKind::Yield { ref mut drop, .. } = term.kind { *drop = Some(to); } else if let TerminatorKind::Drop { ref mut drop, .. } = term.kind { @@ -2146,7 +2143,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { cfg.start_new_cleanup_block() } fn link_entry_point(cfg: &mut CFG<'tcx>, from: BasicBlock, to: BasicBlock) { - let term = &mut cfg.block_data_mut(from).as_mut().left().unwrap().terminator_mut(); + let term = &mut cfg.assert_terminator_mut(from); match &mut term.kind { TerminatorKind::Drop { unwind, .. } => { if let UnwindAction::Cleanup(unwind) = *unwind { From e20623c632dfc34799a544987d4ecaacbf92a15b Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Tue, 10 Nov 2026 19:10:56 +0100 Subject: [PATCH 12/12] Add test for `test_case` --- tests/ui/attributes/duplicated-attributes.rs | 6 ++++ .../attributes/duplicated-attributes.stderr | 34 +++++++++++++------ 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/tests/ui/attributes/duplicated-attributes.rs b/tests/ui/attributes/duplicated-attributes.rs index 0b5840d08da17..31a54fb45a99a 100644 --- a/tests/ui/attributes/duplicated-attributes.rs +++ b/tests/ui/attributes/duplicated-attributes.rs @@ -7,6 +7,7 @@ #![feature(test)] #![feature(cfg_eval)] +#![feature(custom_test_frameworks)] #[test] #[test] @@ -37,6 +38,11 @@ fn f4(_: &mut Bencher) {} //~^ WARNING multiple `test` attributes fn f5() {} +#[test_case] +#[test_case] +//~^ WARNING multiple `test_case` attributes +struct T; + #[cfg_eval] #[cfg_eval] //~^ WARNING multiple `cfg_eval` attributes diff --git a/tests/ui/attributes/duplicated-attributes.stderr b/tests/ui/attributes/duplicated-attributes.stderr index dbffb9a5ef470..1230b90127a90 100644 --- a/tests/ui/attributes/duplicated-attributes.stderr +++ b/tests/ui/attributes/duplicated-attributes.stderr @@ -1,63 +1,75 @@ warning: multiple `test` attributes - --> $DIR/duplicated-attributes.rs:12:1 + --> $DIR/duplicated-attributes.rs:13:1 | LL | #[test] | ^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/duplicated-attributes.rs:11:1 + --> $DIR/duplicated-attributes.rs:12:1 | LL | #[test] | ^^^^^^^ = note: `#[warn(duplicate_macro_attributes)]` on by default warning: multiple `bench` attributes - --> $DIR/duplicated-attributes.rs:29:1 + --> $DIR/duplicated-attributes.rs:30:1 | LL | #[bench] | ^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/duplicated-attributes.rs:28:1 + --> $DIR/duplicated-attributes.rs:29:1 | LL | #[bench] | ^^^^^^^^ warning: multiple `test` attributes - --> $DIR/duplicated-attributes.rs:34:1 + --> $DIR/duplicated-attributes.rs:35:1 | LL | #[test] | ^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/duplicated-attributes.rs:33:1 + --> $DIR/duplicated-attributes.rs:34:1 | LL | #[test] | ^^^^^^^ warning: multiple `test` attributes - --> $DIR/duplicated-attributes.rs:36:1 + --> $DIR/duplicated-attributes.rs:37:1 | LL | #[test] | ^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/duplicated-attributes.rs:34:1 + --> $DIR/duplicated-attributes.rs:35:1 | LL | #[test] | ^^^^^^^ -warning: multiple `cfg_eval` attributes +warning: multiple `test_case` attributes + --> $DIR/duplicated-attributes.rs:42:1 + | +LL | #[test_case] + | ^^^^^^^^^^^^ help: remove this attribute + | +note: attribute also specified here --> $DIR/duplicated-attributes.rs:41:1 | +LL | #[test_case] + | ^^^^^^^^^^^^ + +warning: multiple `cfg_eval` attributes + --> $DIR/duplicated-attributes.rs:47:1 + | LL | #[cfg_eval] | ^^^^^^^^^^^ help: remove this attribute | note: attribute also specified here - --> $DIR/duplicated-attributes.rs:40:1 + --> $DIR/duplicated-attributes.rs:46:1 | LL | #[cfg_eval] | ^^^^^^^^^^^ -warning: 5 warnings emitted +warning: 6 warnings emitted