From 095f89b1299c66c82cc821061d7b61fb8d98a937 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Bj=C3=B8rnager=20Jensen?= Date: Tue, 14 Oct 2025 20:54:40 +0200 Subject: [PATCH 01/13] Add 'bit' and 'set_bit' methods to integral types; --- library/core/src/num/int_macros.rs | 72 +++++++++++++++++ library/core/src/num/uint_macros.rs | 77 +++++++++++++++++++ library/coretests/tests/lib.rs | 1 + library/coretests/tests/num/int_macros.rs | 53 ++++++++++++- library/coretests/tests/num/uint_macros.rs | 52 +++++++++++++ tests/ui/const-generics/issues/issue-86820.rs | 2 +- 6 files changed, 255 insertions(+), 2 deletions(-) diff --git a/library/core/src/num/int_macros.rs b/library/core/src/num/int_macros.rs index 058c0229aa530..bc8281d5f442a 100644 --- a/library/core/src/num/int_macros.rs +++ b/library/core/src/num/int_macros.rs @@ -56,6 +56,78 @@ macro_rules! int_impl { #[stable(feature = "int_bits_const", since = "1.53.0")] pub const BITS: u32 = <$UnsignedT>::BITS; + /// Tests the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let n = 0b111_1001", stringify!($SelfT), ";")] + /// + /// assert!(n.bit(0)); + /// assert!(!n.bit(1)); + /// assert!(!n.bit(2)); + /// assert!(n.bit(3)); + /// assert!(n.bit(4)); + /// assert!(n.bit(5)); + /// assert!(n.bit(6)); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn bit(self, index: u32) -> bool { + self.cast_unsigned().bit(index) + } + + /// Sets the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let mut n = 0b101_0101", stringify!($SelfT), ";")] + /// + /// n = n.set_bit(0, false); + /// n = n.set_bit(1, true); + /// n = n.set_bit(2, false); + /// n = n.set_bit(3, true); + /// n = n.set_bit(4, false); + /// n = n.set_bit(5, true); + /// n = n.set_bit(6, false); + /// + /// assert_eq!(n, 0b010_1010); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn set_bit(self, index: u32, value: bool) -> Self { + self.cast_unsigned().set_bit(index, value).cast_signed() + } + /// Returns the number of ones in the binary representation of `self`. /// /// # Examples diff --git a/library/core/src/num/uint_macros.rs b/library/core/src/num/uint_macros.rs index fcda1edcf4cb2..72f5e023711f1 100644 --- a/library/core/src/num/uint_macros.rs +++ b/library/core/src/num/uint_macros.rs @@ -60,6 +60,83 @@ macro_rules! uint_impl { #[stable(feature = "int_bits_const", since = "1.53.0")] pub const BITS: u32 = Self::MAX.count_ones(); + /// Tests the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal to [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let n = 0b0111_1001", stringify!($SelfT), ";")] + /// + /// assert!(n.bit(0)); + /// assert!(!n.bit(1)); + /// assert!(!n.bit(2)); + /// assert!(n.bit(3)); + /// assert!(n.bit(4)); + /// assert!(n.bit(5)); + /// assert!(n.bit(6)); + /// assert!(!n.bit(7)); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn bit(self, index: u32) -> bool { + (self & (1 << index)) != 0 + } + + /// Sets the value of a specific bit. + /// + /// # Panics + /// + /// If overflow checks are enabled, this method will panic if the provided index is + /// greater than or equal [`BITS`]. + /// + /// [`BITS`]: Self::BITS + /// + /// # Examples + /// + /// ``` + /// #![feature(get_set_bit)] + /// + #[doc = concat!("let mut n = 0b0101_0101", stringify!($SelfT), ";")] + /// + /// n = n.set_bit(0, false); + /// n = n.set_bit(1, true); + /// n = n.set_bit(2, false); + /// n = n.set_bit(3, true); + /// n = n.set_bit(4, false); + /// n = n.set_bit(5, true); + /// n = n.set_bit(6, false); + /// n = n.set_bit(7, true); + /// + /// assert_eq!(n, 0b1010_1010); + /// ``` + #[unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_const_unstable(feature = "get_set_bit", issue = "147702")] + #[rustc_inherit_overflow_checks] + #[inline] + #[must_use = "this returns the result of the operation, \ + without modifying the original"] + #[track_caller] + pub const fn set_bit(mut self, index: u32, value: bool) -> Self { + self &= !(1 << index); + self |= (value as $SelfT) << index; + + self + } + /// Returns the number of ones in the binary representation of `self`. /// /// # Examples diff --git a/library/coretests/tests/lib.rs b/library/coretests/tests/lib.rs index 1fdb0c820d662..665e464fd8ff0 100644 --- a/library/coretests/tests/lib.rs +++ b/library/coretests/tests/lib.rs @@ -66,6 +66,7 @@ #![feature(freeze)] #![feature(future_join)] #![feature(generic_assert_internals)] +#![feature(get_set_bit)] #![feature(hasher_prefixfree_extras)] #![feature(hashmap_internals)] #![feature(int_from_ascii)] diff --git a/library/coretests/tests/num/int_macros.rs b/library/coretests/tests/num/int_macros.rs index f391fe23cd1f5..6165edc6dda1e 100644 --- a/library/coretests/tests/num/int_macros.rs +++ b/library/coretests/tests/num/int_macros.rs @@ -32,7 +32,6 @@ macro_rules! int_module { } test_runtime_and_compiletime! { - fn test_rem_euclid() { assert_eq_const_safe!($T: (-1 as $T).rem_euclid(MIN), MAX); } @@ -73,6 +72,58 @@ macro_rules! int_module { const _1: $T = !0; test_runtime_and_compiletime! { + fn test_bit() { + assert!(!A.bit(0)); + assert!(!A.bit(1)); + assert!(A.bit(2)); + assert!(A.bit(3)); + assert!(!A.bit(4)); + assert!(A.bit(5)); + assert!(!A.bit(6)); + + assert!(B.bit(0)); + assert!(!B.bit(1)); + assert!(!B.bit(2)); + assert!(!B.bit(3)); + assert!(!B.bit(4)); + assert!(B.bit(5)); + assert!(!B.bit(6)); + + assert!(C.bit(0)); + assert!(!C.bit(1)); + assert!(!C.bit(2)); + assert!(C.bit(3)); + assert!(C.bit(4)); + assert!(C.bit(5)); + assert!(C.bit(6)); + } + + fn test_set_bit() { + assert_eq_const_safe!($T: A.set_bit(0, true), 0b0101101); + assert_eq_const_safe!($T: A.set_bit(1, true), 0b0101110); + assert_eq_const_safe!($T: A.set_bit(2, false), 0b0101000); + assert_eq_const_safe!($T: A.set_bit(3, false), 0b0100100); + assert_eq_const_safe!($T: A.set_bit(4, true), 0b0111100); + assert_eq_const_safe!($T: A.set_bit(5, false), 0b0001100); + assert_eq_const_safe!($T: A.set_bit(6, true), 0b1101100); + + assert_eq_const_safe!($T: B.set_bit(0, false), 0b0100000); + assert_eq_const_safe!($T: B.set_bit(1, true), 0b0100011); + assert_eq_const_safe!($T: B.set_bit(2, true), 0b0100101); + assert_eq_const_safe!($T: B.set_bit(3, true), 0b0101001); + assert_eq_const_safe!($T: B.set_bit(4, true), 0b0110001); + assert_eq_const_safe!($T: B.set_bit(5, false), 0b0000001); + assert_eq_const_safe!($T: B.set_bit(6, true), 0b1100001); + + assert_eq_const_safe!($T: C.set_bit(0, false), 0b1111000); + assert_eq_const_safe!($T: C.set_bit(1, true), 0b1111011); + assert_eq_const_safe!($T: C.set_bit(2, true), 0b1111101); + assert_eq_const_safe!($T: C.set_bit(3, false), 0b1110001); + assert_eq_const_safe!($T: C.set_bit(4, false), 0b1101001); + assert_eq_const_safe!($T: C.set_bit(5, false), 0b1011001); + assert_eq_const_safe!($T: C.set_bit(6, false), 0b0111001); + } + fn test_count_ones() { assert_eq_const_safe!(u32: A.count_ones(), 3); assert_eq_const_safe!(u32: B.count_ones(), 2); diff --git a/library/coretests/tests/num/uint_macros.rs b/library/coretests/tests/num/uint_macros.rs index 4dfcb1b8b4688..9d500d1578b64 100644 --- a/library/coretests/tests/num/uint_macros.rs +++ b/library/coretests/tests/num/uint_macros.rs @@ -35,6 +35,58 @@ macro_rules! uint_module { const _1: $T = !0; test_runtime_and_compiletime! { + fn test_bit() { + assert!(!A.bit(0)); + assert!(!A.bit(1)); + assert!(A.bit(2)); + assert!(A.bit(3)); + assert!(!A.bit(4)); + assert!(A.bit(5)); + assert!(!A.bit(6)); + + assert!(B.bit(0)); + assert!(!B.bit(1)); + assert!(!B.bit(2)); + assert!(!B.bit(3)); + assert!(!B.bit(4)); + assert!(B.bit(5)); + assert!(!B.bit(6)); + + assert!(C.bit(0)); + assert!(!C.bit(1)); + assert!(!C.bit(2)); + assert!(C.bit(3)); + assert!(C.bit(4)); + assert!(C.bit(5)); + assert!(C.bit(6)); + } + + fn test_set_bit() { + assert_eq_const_safe!($T: A.set_bit(0, true), 0b0101101); + assert_eq_const_safe!($T: A.set_bit(1, true), 0b0101110); + assert_eq_const_safe!($T: A.set_bit(2, false), 0b0101000); + assert_eq_const_safe!($T: A.set_bit(3, false), 0b0100100); + assert_eq_const_safe!($T: A.set_bit(4, true), 0b0111100); + assert_eq_const_safe!($T: A.set_bit(5, false), 0b0001100); + assert_eq_const_safe!($T: A.set_bit(6, true), 0b1101100); + + assert_eq_const_safe!($T: B.set_bit(0, false), 0b0100000); + assert_eq_const_safe!($T: B.set_bit(1, true), 0b0100011); + assert_eq_const_safe!($T: B.set_bit(2, true), 0b0100101); + assert_eq_const_safe!($T: B.set_bit(3, true), 0b0101001); + assert_eq_const_safe!($T: B.set_bit(4, true), 0b0110001); + assert_eq_const_safe!($T: B.set_bit(5, false), 0b0000001); + assert_eq_const_safe!($T: B.set_bit(6, true), 0b1100001); + + assert_eq_const_safe!($T: C.set_bit(0, false), 0b1111000); + assert_eq_const_safe!($T: C.set_bit(1, true), 0b1111011); + assert_eq_const_safe!($T: C.set_bit(2, true), 0b1111101); + assert_eq_const_safe!($T: C.set_bit(3, false), 0b1110001); + assert_eq_const_safe!($T: C.set_bit(4, false), 0b1101001); + assert_eq_const_safe!($T: C.set_bit(5, false), 0b1011001); + assert_eq_const_safe!($T: C.set_bit(6, false), 0b0111001); + } + fn test_count_ones() { assert!(A.count_ones() == 3); assert!(B.count_ones() == 2); diff --git a/tests/ui/const-generics/issues/issue-86820.rs b/tests/ui/const-generics/issues/issue-86820.rs index c2c0c1a8d0390..51a160cb213b3 100644 --- a/tests/ui/const-generics/issues/issue-86820.rs +++ b/tests/ui/const-generics/issues/issue-86820.rs @@ -5,7 +5,7 @@ use std::ops::BitAnd; const C: fn() = || is_set(); fn is_set() { - 0xffu8.bit::<0>(); + Bits::bit::<0>(0xffu8); } trait Bits { From a812bac95f78a64be030b4d2a8b67fe9c1e2ef67 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 30 Sep 2026 16:47:05 +0200 Subject: [PATCH 02/13] Render `Deref` methods on aliased items --- src/librustdoc/html/render/mod.rs | 95 +++++++-- src/librustdoc/html/render/print_item.rs | 30 ++- src/librustdoc/html/render/sidebar.rs | 182 ++++++++++-------- src/librustdoc/html/render/write_shared.rs | 11 +- .../deref/deref-methods-aliased-type.rs | 87 +++++++++ tests/rustdoc-html/deref/deref-on-aliased.rs | 30 +++ tests/rustdoc-html/typedef/deref-32077.rs | 2 +- 7 files changed, 324 insertions(+), 113 deletions(-) create mode 100644 tests/rustdoc-html/deref/deref-methods-aliased-type.rs create mode 100644 tests/rustdoc-html/deref/deref-on-aliased.rs diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index c2d8cc9ebae3e..1ec30da5c81fb 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -59,6 +59,7 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::Mutability; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; +use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::DUMMY_SP; @@ -1467,16 +1468,73 @@ fn render_all_impls( Ok(()) } +// Also used in `write_shared.rs` when generating the JS `impl` file for aliased types. +// +// FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this +// to use type unification. +// Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might +// regress. +pub(crate) fn impl_may_apply_to_type_alias( + tcx: TyCtxt<'_>, + alias_def_id: DefId, + impl_def_id: DefId, +) -> bool { + let alias_ty = tcx.type_of(alias_def_id).skip_binder(); + let for_ty = tcx.type_of(impl_def_id).skip_binder(); + DeepRejectCtxt::relate_infer_infer(tcx).types_may_unify(alias_ty, for_ty) +} + +fn aliased_type_deref_impl<'a>( + cx: &'a Context<'_>, + alias_def_id: DefId, + aliased_def_id: DefId, +) -> Option<(&'a Impl, bool)> { + let tcx = cx.tcx(); + let applies = + |impl_: &Impl| { + impl_.impl_item.item_id.as_def_id().is_some_and(|impl_def_id| { + impl_may_apply_to_type_alias(tcx, alias_def_id, impl_def_id) + }) + }; + let impls = cx.cache().impls.get(&aliased_def_id)?; + let mut deref_impl = None; + let mut has_deref_mut = false; + + for impl_ in impls { + if impl_.trait_did() == tcx.lang_items().deref_trait() { + if !impl_.is_negative_trait_impl() && applies(impl_) { + deref_impl = Some(impl_); + } + } else if impl_.trait_did() == tcx.lang_items().deref_mut_trait() && applies(impl_) { + has_deref_mut = true; + } + } + Some((deref_impl?, has_deref_mut)) +} + fn render_assoc_items( cx: &Context<'_>, containing_item: &clean::Item, it: DefId, what: AssocItemRender<'_>, +) -> impl fmt::Display { + render_assoc_items_with_aliased_type(cx, containing_item, it, what, None) +} + +fn render_assoc_items_with_aliased_type( + cx: &Context<'_>, + containing_item: &clean::Item, + it: DefId, + what: AssocItemRender<'_>, + aliased_type: Option, ) -> impl fmt::Display { fmt::from_fn(move |f| { let mut derefs = DefIdSet::default(); derefs.insert(it); - render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs) + if let Some(aliased_type) = aliased_type { + derefs.insert(aliased_type); + } + render_assoc_items_inner(f, cx, containing_item, it, what, &mut derefs, aliased_type) }) } @@ -1487,10 +1545,15 @@ fn render_assoc_items_inner( it: DefId, what: AssocItemRender<'_>, derefs: &mut DefIdSet, + aliased_type: Option, ) -> fmt::Result { info!("Documenting associated items of {:?}", containing_item.name); let cache = &cx.shared.cache; - let Some(impls) = cache.impls.get(&it) else { return Ok(()) }; + let impls = match cache.impls.get(&it) { + Some(impls) => impls.as_slice(), + None if aliased_type.is_some() => &[], + None => return Ok(()), + }; let (mut inherent_impls, trait_impls): (Vec<_>, _) = impls.iter().partition(|i| i.inner_impl().trait_.is_none()); if !inherent_impls.is_empty() { @@ -1578,17 +1641,21 @@ fn render_assoc_items_inner( } } - if !trait_impls.is_empty() { - let deref_impl = trait_impls.iter().find(|t| { - t.trait_did() == cx.tcx().lang_items().deref_trait() && !t.is_negative_trait_impl() - }); - if let Some(impl_) = deref_impl { - let has_deref_mut = trait_impls - .iter() - .any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; - } + let deref_impl = trait_impls.iter().find(|t| { + t.trait_did() == cx.tcx().lang_items().deref_trait() && !t.is_negative_trait_impl() + }); + let deref = if let Some(impl_) = deref_impl { + let has_deref_mut = + trait_impls.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + Some((*impl_, has_deref_mut)) + } else { + aliased_type.and_then(|aliased_did| aliased_type_deref_impl(cx, it, aliased_did)) + }; + if let Some((impl_, has_deref_mut)) = deref { + render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; + } + if !trait_impls.is_empty() { // If we were already one level into rendering deref methods, we don't want to render // anything after recursing into any further deref methods above. if let AssocItemRender::DerefFor { .. } = what { @@ -1646,11 +1713,11 @@ fn render_deref_methods( return Ok(()); } } - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs, None)?; } else if let Some(prim) = target.primitive_type() && let Some(&did) = cache.primitive_locations.get(&prim) { - render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs)?; + render_assoc_items_inner(&mut w, cx, container_item, did, what, derefs, None)?; } Ok(()) } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 544d57d92613c..b34a778889728 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -23,9 +23,9 @@ use super::{ AssocItemLink, AssocItemRender, Context, ImplRenderingParameters, RenderMode, collect_paths_for_type, document, ensure_trailing_slash, get_filtered_impls_for_reference, item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, - render_assoc_item, render_assoc_items, render_attributes_in_code, render_impl, - render_repr_attribute_in_code, render_rightside, render_stability_since_raw, - render_stability_since_raw_with_extra, write_section_heading, + render_assoc_item, render_assoc_items, render_assoc_items_with_aliased_type, + render_attributes_in_code, render_impl, render_repr_attribute_in_code, render_rightside, + render_stability_since_raw, render_stability_since_raw_with_extra, write_section_heading, }; use crate::clean; use crate::config::ModuleSorting; @@ -1499,7 +1499,13 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { fn render_assoc_items(&self) -> impl fmt::Display { let def_id = self.it.item_id.expect_def_id(); - render_assoc_items(self.cx, self.it, def_id, AssocItemRender::All) + render_assoc_items_with_aliased_type( + self.cx, + self.it, + def_id, + AssocItemRender::All, + if self.is_type_alias { Some(self.def_id) } else { None }, + ) } fn render_union(&self) -> impl Display { @@ -1642,7 +1648,13 @@ impl<'clean> DisplayEnum<'clean> { write!( w, "{}{}", - render_assoc_items(cx, it, def_id, AssocItemRender::All), + render_assoc_items_with_aliased_type( + cx, + it, + def_id, + AssocItemRender::All, + if is_type_alias { Some(self.def_id) } else { None }, + ), document_type_layout(cx, layout_def_id) ) } @@ -2098,7 +2110,13 @@ impl<'a> DisplayStruct<'a> { w, "{}{}{}", item_fields(cx, it, self.fields, self.ctor_kind), - render_assoc_items(cx, it, def_id, AssocItemRender::All), + render_assoc_items_with_aliased_type( + cx, + it, + def_id, + AssocItemRender::All, + if is_type_alias { Some(self.def_id) } else { None }, + ), document_type_layout(cx, def_id), ) } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 7b282b0c61d7e..2adde5b80dc56 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -5,7 +5,7 @@ use std::fmt; use askama::Template; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir::def::CtorKind; -use rustc_hir::def_id::{DefIdMap, DefIdSet}; +use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet}; use tracing::debug; use super::{Context, ItemSection, impl_trait_key, item_ty_to_section}; @@ -284,7 +284,7 @@ fn sidebar_struct<'a>( if let Some(name) = field_name { items.push(LinkBlock::new(Link::new("fields", name), "structfield", fields)); } - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } fn sidebar_trait<'a>( @@ -352,7 +352,7 @@ fn sidebar_trait<'a>( blocks.push(LinkBlock::new(Link::new("provided-methods", "Provided Methods"), "", prov_method)); - sidebar_assoc_items(cx, it, blocks, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), blocks, deref_id_map); if !t.is_dyn_compatible(tcx) { blocks.push(LinkBlock::forced( @@ -383,7 +383,7 @@ fn sidebar_primitive<'a>( deref_id_map: &'a DefIdMap, ) { if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } else { let (concrete, synthetic, blanket_impl) = super::get_filtered_impls_for_reference(&cx.shared, it); @@ -420,7 +420,17 @@ fn sidebar_type_alias<'a>( } } } - sidebar_assoc_items(cx, it, items, deref_id_map); + let alias_def_id = it.item_id.expect_def_id(); + let aliased_type = t.inner_type.as_ref().and_then(|_| { + cx.tcx().type_of(alias_def_id).instantiate_identity().skip_norm_wip().ty_adt_def() + }); + sidebar_assoc_items_with_aliased_type( + cx, + alias_def_id, + items, + deref_id_map, + aliased_type.map(|adt| adt.did()), + ); } fn sidebar_union<'a>( @@ -432,106 +442,115 @@ fn sidebar_union<'a>( ) { let fields = get_struct_fields_name(&u.fields); items.push(LinkBlock::new(Link::new("fields", "Fields"), "structfield", fields)); - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } /// Adds trait implementations into the blocks of links fn sidebar_assoc_items<'a>( cx: &'a Context<'_>, - it: &'a clean::Item, + item_def_id: DefId, + links: &mut Vec>, + deref_id_map: &'a DefIdMap, +) { + sidebar_assoc_items_with_aliased_type(cx, item_def_id, links, deref_id_map, None) +} + +fn sidebar_assoc_items_with_aliased_type<'a>( + cx: &'a Context<'_>, + item_def_id: DefId, links: &mut Vec>, deref_id_map: &'a DefIdMap, + aliased_type: Option, ) { - let did = it.item_id.expect_def_id(); let cache = cx.cache(); + let v = match cache.impls.get(&item_def_id) { + Some(impls) => impls.as_slice(), + None if aliased_type.is_some() => &[], + None => return, + }; let mut assoc_consts = Vec::new(); let mut assoc_types = Vec::new(); let mut assoc_fns = Vec::new(); let mut methods = Vec::new(); - if let Some(v) = cache.impls.get(&did) { - let mut used_links = UsedLinks::default(); - let mut id_map = IdMap::new(); + let mut used_links = UsedLinks::default(); + let mut id_map = IdMap::new(); - { - let used_links_bor = &mut used_links; - for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) { - assoc_consts.extend(get_associated_constants(impl_, used_links_bor)); - assoc_types.extend(get_associated_types(impl_, used_links_bor)); - methods.extend(get_methods( - impl_, - GetMethodsMode::AlsoCollectAssocFns { assoc_fns: &mut assoc_fns }, - used_links_bor, - cx, - )); - } - // We want links' order to be reproducible so we don't use unstable sort. - assoc_consts.sort(); - assoc_types.sort(); - postprocess_method_list(&mut assoc_fns); - postprocess_method_list(&mut methods); + { + let used_links_bor = &mut used_links; + for impl_ in v.iter().map(|i| i.inner_impl()).filter(|i| i.trait_.is_none()) { + assoc_consts.extend(get_associated_constants(impl_, used_links_bor)); + assoc_types.extend(get_associated_types(impl_, used_links_bor)); + methods.extend(get_methods( + impl_, + GetMethodsMode::AlsoCollectAssocFns { assoc_fns: &mut assoc_fns }, + used_links_bor, + cx, + )); } + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + assoc_types.sort(); + postprocess_method_list(&mut assoc_fns); + postprocess_method_list(&mut methods); + } - let mut blocks = vec![ - LinkBlock::new( - Link::new("implementations", "Associated Constants"), - "associatedconstant", - assoc_consts, - ), - LinkBlock::new( - Link::new("implementations", "Associated Types"), - "associatedtype", - assoc_types, - ), - LinkBlock::new( - Link::new("implementations", "Associated Functions"), - "method", - assoc_fns, - ), - LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), - ]; - - if v.iter().any(|i| i.inner_impl().trait_.is_some()) { - if let Some(impl_) = v.iter().find(|i| { - i.trait_did() == cx.tcx().lang_items().deref_trait() && !i.is_negative_trait_impl() - }) { - let mut derefs = DefIdSet::default(); - derefs.insert(did); - sidebar_deref_methods( - cx, - &mut blocks, - impl_, - v, - &mut derefs, - &mut used_links, - deref_id_map, - ); - } + let mut blocks = vec![ + LinkBlock::new( + Link::new("implementations", "Associated Constants"), + "associatedconstant", + assoc_consts, + ), + LinkBlock::new( + Link::new("implementations", "Associated Types"), + "associatedtype", + assoc_types, + ), + LinkBlock::new(Link::new("implementations", "Associated Functions"), "method", assoc_fns), + LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), + ]; - let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = - v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = - concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); + let deref = if let Some(impl_) = v.iter().find(|i| { + i.trait_did() == cx.tcx().lang_items().deref_trait() && !i.is_negative_trait_impl() + }) { + let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + Some((impl_, deref_mut)) + } else { + aliased_type + .and_then(|aliased_did| super::aliased_type_deref_impl(cx, item_def_id, aliased_did)) + }; + if let Some((impl_, deref_mut)) = deref { + let mut derefs = DefIdSet::default(); + derefs.insert(item_def_id); + derefs.extend(aliased_type); + sidebar_deref_methods( + cx, + &mut blocks, + impl_, + deref_mut, + &mut derefs, + &mut used_links, + deref_id_map, + ); + } - sidebar_render_assoc_items( - cx, - &mut id_map, - concrete, - synthetic, - blanket_impl, - &mut blocks, - ); - } + if v.iter().any(|i| i.inner_impl().trait_.is_some()) { + let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = + v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = + concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); - links.append(&mut blocks); + sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl, &mut blocks); } + + links.append(&mut blocks); } fn sidebar_deref_methods<'a>( cx: &'a Context<'_>, out: &mut Vec>, impl_: &Impl, - v: &[Impl], + deref_mut: bool, derefs: &mut DefIdSet, used_links: &mut UsedLinks, deref_id_map: &'a DefIdMap, @@ -558,7 +577,6 @@ fn sidebar_deref_methods<'a>( return; } let tcx = cx.tcx(); - let deref_mut = v.iter().any(|i| i.trait_did() == tcx.lang_items().deref_mut_trait()); let inner_impl = target .def_id(c) .or_else(|| { @@ -623,7 +641,7 @@ fn sidebar_deref_methods<'a>( cx, out, target_deref_impl, - target_impls, + deref_mut, derefs, used_links, deref_id_map, @@ -647,7 +665,7 @@ fn sidebar_enum<'a>( variants.sort_unstable(); items.push(LinkBlock::new(Link::new("variants", "Variants"), "variant", variants)); - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } pub(crate) fn sidebar_module_like( @@ -708,7 +726,7 @@ fn sidebar_foreign_type<'a>( items: &mut Vec>, deref_id_map: &'a DefIdMap, ) { - sidebar_assoc_items(cx, it, items, deref_id_map); + sidebar_assoc_items(cx, it.item_id.expect_def_id(), items, deref_id_map); } /// Renders the trait implementations for this type diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 258970bbb3b8f..9be744edee6cb 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -28,7 +28,6 @@ use indexmap::IndexMap; use rustc_ast::join_path_syms; use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_middle::ty::TyCtxt; -use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_session::Session; use rustc_span::Symbol; use rustc_span::def_id::DefId; @@ -1015,7 +1014,6 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { let Some(self_fqp) = cache.exact_paths.get(&self_did).or_else(get_local) else { return; }; - let aliased_ty = self.cx.tcx().type_of(self_did).skip_binder(); // Exclude impls that are directly on this type. They're already in the HTML. // Some inlining scenarios can cause there to be two versions of the same // impl: one on the type alias and one on the underlying target type. @@ -1024,15 +1022,8 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { for (impl_item_id, aliased_type_impl) in &mut aliased_type.impl_ { // Only include this impl if it actually unifies with this alias. // Synthetic impls are not included; those are also included in the HTML. - // - // FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this - // to use type unification. - // Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might - // regress. let Some(impl_did) = impl_item_id.as_def_id() else { continue }; - let for_ty = self.cx.tcx().type_of(impl_did).skip_binder(); - let reject_cx = DeepRejectCtxt::relate_infer_infer(self.cx.tcx()); - if !reject_cx.types_may_unify(aliased_ty, for_ty) { + if !super::impl_may_apply_to_type_alias(self.cx.tcx(), self_did, impl_did) { continue; } // Avoid duplicates diff --git a/tests/rustdoc-html/deref/deref-methods-aliased-type.rs b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs new file mode 100644 index 0000000000000..5e0c9a1d4e769 --- /dev/null +++ b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs @@ -0,0 +1,87 @@ +// Ensures that aliased types get the "Methods from Deref" but only when it matches +// the aliased type. +// Regression test for . + +// ignore-tidy-file-linelength + +#![crate_name = "foo"] + +use std::ops::{Deref, DerefMut}; + +pub struct Target; + +impl Target { + pub fn target_ref(&self) {} + pub fn target_mut(&mut self) {} +} + +pub struct Other; + +impl Other { + pub fn other_ref(&self) {} + pub fn other_mut(&mut self) {} +} + +pub struct Wrapper(T); + +impl Deref for Wrapper { + type Target = Target; + fn deref(&self) -> &Target { + &Target + } +} + +impl Deref for Wrapper { + type Target = Other; + fn deref(&self) -> &Other { + &Other + } +} + +impl DerefMut for Wrapper { + fn deref_mut(&mut self) -> &mut Other { + &mut Other + } +} + +//@ has 'foo/type.Byte.html' +// Only one deref. +//@ count - '//h2[@id="deref-methods-Target"]' 1 +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 2 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +//@ has - '//*[@class="section-header"]' 'Methods from Deref' +// Only one method from the deref (not the one taking `&mut` since `DerefMut` is not +// implemented for `Wrapper`. +//@ count - '//*[@id="deref-methods-Target-1"]/*[@class="method"]' 1 +//@ has - '//*[@id="method.target_ref"]//h4' 'pub fn target_ref(&self)' + +// Same checks but in the sidebar. +//@ count - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a' 1 +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.target_ref"]' 'target_ref' +pub type Byte = Wrapper; + +//@ has 'foo/type.Word.html' +// There should be only one `Deref` block (for both `Deref` and `DerefMut`). +//@ count - '//h2[@id="deref-methods-Other"]' 1 +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 2 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +//@ has - '//*[@class="section-header"]' 'Methods from Deref' +// Since it implements both `Deref` and `DerefMut`, we should see both methods. +//@ count - '//*[@id="deref-methods-Other-1"]/*[@class="method"]' 2 +//@ has - '//*[@id="method.other_ref"]//h4' 'pub fn other_ref(&self)' +//@ has - '//*[@id="method.other_mut"]//h4' 'pub fn other_mut(&mut self)' + +// Same checks but in the sidebar. +//@ count - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a' 2 +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.other_ref"]' 'other_ref' +//@ has - '//*[@class="sidebar-elems"]//*[@class="block deref-methods"]//a[@href="#method.other_mut"]' 'other_mut' +pub type Word = Wrapper; + +//@ has 'foo/type.Dword.html' +// There is no `Deref` implementation for `Wrapper` so we confirm that. +// Which makes two section headers. One for the "Aliased Type" and one for "Methods from Deref<>". +//@ count - '//*[@class="section-header"]' 1 +//@ has - '//*[@class="section-header"]' 'Aliased Type' +pub type Dword = Wrapper; diff --git a/tests/rustdoc-html/deref/deref-on-aliased.rs b/tests/rustdoc-html/deref/deref-on-aliased.rs new file mode 100644 index 0000000000000..cf9f189ecc710 --- /dev/null +++ b/tests/rustdoc-html/deref/deref-on-aliased.rs @@ -0,0 +1,30 @@ +// Ensures that aliased types also get the "methods from deref" content. +// Regression test for . + +#![crate_name = "foo"] + +use std::ops::Deref; + +// First we check for the `Col` struct. +//@ has 'foo/struct.Col.html' +//@ has - '//*[@id="main-content"]//h2[@id="deref-methods-%5BT%5D"]' 'Methods from Deref' +//@ has - '//*[@id="rustdoc-toc"]//a[@href="#deref-methods-%5BT%5D"]' 'Methods from Deref' + +pub struct Col { + data: [T; N], +} + +// Then for the type alias. +//@ has 'foo/type.Mat.html' +//@ has - '//*[@id="main-content"]//h2[@id="deref-methods-%5BT%5D"]' 'Methods from Deref' +//@ has - '//*[@id="rustdoc-toc"]//a[@href="#deref-methods-%5BT%5D"]' 'Methods from Deref' + +pub type Mat = Col; + +impl Deref for Col { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + &self.data + } +} diff --git a/tests/rustdoc-html/typedef/deref-32077.rs b/tests/rustdoc-html/typedef/deref-32077.rs index faab4b6f522ba..5c6949936e43e 100644 --- a/tests/rustdoc-html/typedef/deref-32077.rs +++ b/tests/rustdoc-html/typedef/deref-32077.rs @@ -54,7 +54,7 @@ impl std::ops::Deref for GenericStruct { pub struct Wrap(GenericStruct); //@ has 'foo/type.Alias.html' -//@ !has - '//h2' 'Methods from Deref' +//@ has - '//h2' 'Methods from Deref' //@ !has - '//*[@id="impl-Deref-for-Wrap%3CT%3E"]/h3' 'impl Deref for Wrap' //@ hasraw 'type.impl/foo/struct.Wrap.js' 'impl-Deref-for-Wrap%3CT%3E' // Deref Methods aren't gathered for type aliases, though the actual impl is. From 0c442eaf515547619964604ffe82daa26c335215 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 2 Oct 2026 12:30:39 +0200 Subject: [PATCH 03/13] Improve `is_doc_subtype_of` to work better for doc aliases --- src/librustdoc/clean/types.rs | 52 +++++++++++++++------- src/librustdoc/html/render/mod.rs | 37 ++++----------- src/librustdoc/html/render/sidebar.rs | 2 +- src/librustdoc/html/render/write_shared.rs | 3 +- tests/rustdoc-html/deref/not-mut-deref.rs | 35 +++++++++++++++ 5 files changed, 83 insertions(+), 46 deletions(-) create mode 100644 tests/rustdoc-html/deref/not-mut-deref.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c34b33d9c92f5..224c3851448d3 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -21,7 +21,7 @@ use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::{BodyId, Mutability}; use rustc_index::IndexVec; use rustc_metadata::rendered_const; -use rustc_middle::ty::fast_reject::SimplifiedType; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, SimplifiedType}; use rustc_middle::ty::{self, Ty, TyCtxt, Visibility}; use rustc_resolve::rustdoc::{ DocFragment, add_doc_fragment, attrs_to_doc_fragments, span_of_fragments, @@ -1380,6 +1380,22 @@ pub(crate) struct PolyTrait { pub(crate) generic_params: Vec, } +// Also used in `write_shared.rs` when generating the JS `impl` file for aliased types. +// +// FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this +// to use type unification. +// Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might +// regress. +pub(crate) fn impl_may_apply_to_type_alias( + tcx: TyCtxt<'_>, + alias_def_id: DefId, + impl_def_id: DefId, +) -> bool { + let alias_ty = tcx.type_of(alias_def_id).skip_binder(); + let for_ty = tcx.type_of(impl_def_id).skip_binder(); + DeepRejectCtxt::relate_infer_infer(tcx).types_may_unify(alias_ty, for_ty) +} + /// Rustdoc's representation of types, mostly based on the [`hir::Ty`]. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) enum Type { @@ -1469,7 +1485,7 @@ impl Type { /// /// An owned type is also the same as its borrowed variants (this is commutative), /// but `&T` is not the same as `&mut T`. - pub(crate) fn is_doc_subtype_of(&self, other: &Self, cache: &Cache) -> bool { + pub(crate) fn is_doc_subtype_of(&self, other: &Self, cx: &Context<'_>) -> bool { // Strip the references so that it can compare the actual types, unless both are references. // If both are references, leave them alone and compare the mutabilities later. let (self_cleared, other_cleared) = if !self.is_borrowed_ref() || !other.is_borrowed_ref() { @@ -1478,29 +1494,33 @@ impl Type { (self, other) }; - // FIXME: `Cache` does not have the data required to unwrap type aliases, - // so we just assume they are equal. - // This is only remotely acceptable because we were previously - // assuming all types were equal when used - // as a generic parameter of a type in `Deref::Target`. + // FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this + // to use type unification. + // Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might + // regress. if self_cleared.is_type_alias() || other_cleared.is_type_alias() { - return true; + let cache = cx.cache(); + if let Some(self_def_id) = self_cleared.def_id(&cache) + && let Some(other_def_id) = other_cleared.def_id(&cache) + { + return impl_may_apply_to_type_alias(cx.tcx(), self_def_id, other_def_id); + } } match (self_cleared, other_cleared) { // Recursive cases. (Type::Tuple(a), Type::Tuple(b)) => { - a.iter().eq_by(b, |a, b| a.is_doc_subtype_of(b, cache)) + a.iter().eq_by(b, |a, b| a.is_doc_subtype_of(b, cx)) } - (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cache), - (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cache), + (Type::Slice(a), Type::Slice(b)) => a.is_doc_subtype_of(b, cx), + (Type::Array(a, al), Type::Array(b, bl)) => al == bl && a.is_doc_subtype_of(b, cx), (Type::RawPointer(mutability, type_), Type::RawPointer(b_mutability, b_type_)) => { - mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache) + mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cx) } ( Type::BorrowedRef { mutability, type_, .. }, Type::BorrowedRef { mutability: b_mutability, type_: b_type_, .. }, - ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cache), + ) => mutability == b_mutability && type_.is_doc_subtype_of(b_type_, cx), // Placeholders are equal to all other types. (Type::Infer, _) | (_, Type::Infer) => true, // Generics match everything on the right, but not on the left. @@ -1514,13 +1534,13 @@ impl Type { a.def_id() == b.def_id() && a.generics() .zip(b.generics()) - .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cache))) + .map(|(ag, bg)| ag.zip(bg).all(|(at, bt)| at.is_doc_subtype_of(bt, cx))) .unwrap_or(true) } // Other cases, such as primitives, just use recursion. (a, b) => a - .def_id(cache) - .and_then(|a| Some((a, b.def_id(cache)?))) + .def_id(cx.cache()) + .and_then(|a| Some((a, b.def_id(cx.cache())?))) .map(|(a, b)| a == b) .unwrap_or(false), } diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 1ec30da5c81fb..8a08dba136bb7 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -59,7 +59,6 @@ use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet}; use rustc_hir::Mutability; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, DefIdSet}; -use rustc_middle::ty::fast_reject::DeepRejectCtxt; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, TyCtxt}; use rustc_span::DUMMY_SP; @@ -1468,34 +1467,17 @@ fn render_all_impls( Ok(()) } -// Also used in `write_shared.rs` when generating the JS `impl` file for aliased types. -// -// FIXME(checked_type_alias): Once the feature is complete or stable, rewrite this -// to use type unification. -// Be aware of `tests/rustdoc-html/type-alias/deeply-nested-112515.rs` which might -// regress. -pub(crate) fn impl_may_apply_to_type_alias( - tcx: TyCtxt<'_>, - alias_def_id: DefId, - impl_def_id: DefId, -) -> bool { - let alias_ty = tcx.type_of(alias_def_id).skip_binder(); - let for_ty = tcx.type_of(impl_def_id).skip_binder(); - DeepRejectCtxt::relate_infer_infer(tcx).types_may_unify(alias_ty, for_ty) -} - fn aliased_type_deref_impl<'a>( cx: &'a Context<'_>, alias_def_id: DefId, aliased_def_id: DefId, ) -> Option<(&'a Impl, bool)> { let tcx = cx.tcx(); - let applies = - |impl_: &Impl| { - impl_.impl_item.item_id.as_def_id().is_some_and(|impl_def_id| { - impl_may_apply_to_type_alias(tcx, alias_def_id, impl_def_id) - }) - }; + let applies = |impl_: &Impl| { + impl_.impl_item.item_id.as_def_id().is_some_and(|impl_def_id| { + clean::types::impl_may_apply_to_type_alias(tcx, alias_def_id, impl_def_id) + }) + }; let impls = cx.cache().impls.get(&aliased_def_id)?; let mut deref_impl = None; let mut has_deref_mut = false; @@ -1579,9 +1561,8 @@ fn render_assoc_items_inner( // we should not show methods from `[MaybeUninit]`. // this `retain` filters out any instances where // the types do not line up perfectly. - inherent_impls.retain(|impl_| { - type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx.shared.cache) - }); + inherent_impls + .retain(|impl_| type_.is_doc_subtype_of(&impl_.inner_impl().for_, &cx)); let derived_id = cx.derive_id(&id); if let Some(def_id) = type_.def_id(cx.cache()) { cx.deref_id_map.borrow_mut().insert(def_id, id.clone()); @@ -1778,7 +1759,7 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Option) -> (String, String) { .filter(|impl_| impl_.polarity == ty::ImplPolarity::Positive) .filter(|impl_| { // Two different types might have the same did, without actually being the same. - ty.is_doc_subtype_of(&impl_.for_, cx.cache()) + ty.is_doc_subtype_of(&impl_.for_, cx) }) .filter_map(|impl_| { if let Some(trait_) = &impl_.trait_ diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 2adde5b80dc56..05829dd82e0a9 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -591,7 +591,7 @@ fn sidebar_deref_methods<'a>( .iter() .filter(|i| { i.inner_impl().trait_.is_none() - && real_target.is_doc_subtype_of(&i.inner_impl().for_, c) + && real_target.is_doc_subtype_of(&i.inner_impl().for_, cx) }) .flat_map(|i| { get_methods( diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 9be744edee6cb..58d53f5adaf8c 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -36,6 +36,7 @@ use serde::ser::SerializeSeq; use serde::{Deserialize, Serialize, Serializer}; use super::{Context, RenderMode, collect_paths_for_type, ensure_trailing_slash}; +use crate::clean::types::impl_may_apply_to_type_alias; use crate::clean::{Crate, Item, ItemId, ItemKind}; use crate::config::{EmitType, PathToParts, RenderOptions, ShouldMerge}; use crate::docfs::PathError; @@ -1023,7 +1024,7 @@ impl<'item> DocVisitor<'item> for TypeImplCollector<'_, '_, 'item> { // Only include this impl if it actually unifies with this alias. // Synthetic impls are not included; those are also included in the HTML. let Some(impl_did) = impl_item_id.as_def_id() else { continue }; - if !super::impl_may_apply_to_type_alias(self.cx.tcx(), self_did, impl_did) { + if !impl_may_apply_to_type_alias(self.cx.tcx(), self_did, impl_did) { continue; } // Avoid duplicates diff --git a/tests/rustdoc-html/deref/not-mut-deref.rs b/tests/rustdoc-html/deref/not-mut-deref.rs new file mode 100644 index 0000000000000..02aac49e46935 --- /dev/null +++ b/tests/rustdoc-html/deref/not-mut-deref.rs @@ -0,0 +1,35 @@ +// This test ensures that `get_u32` is not listed as the `Deref` impl +// doesn't match it. +// Regression test for . + +#![crate_name = "foo"] + +//@ has 'foo/struct.Bar.html' +//@ count - '//*[@id="deref-methods-Foo%3Ci32%3E-1"]/*[@class="method"]' 1 +//@ has - '//*[@id="deref-methods-Foo%3Ci32%3E-1"]/*[@class="method"]/h4' \ +// 'pub fn get_i32(&self) -> i32' + +// Same check for the sidebar. +//@ count - '//*[@id="rustdoc-toc"]/*[@class="block deref-methods"]//a' 1 +//@ has - '//*[@id="rustdoc-toc"]/*[@class="block deref-methods"]//a' 'get_i32' + +use std::ops::Deref; + +pub struct Foo(T); + +impl Foo { + pub fn get_i32(&self) -> i32 { self.0 } +} + +impl Foo { + pub fn get_u32(&self) -> u32 { self.0 } +} + +pub struct Bar(Foo); + +impl Deref for Bar { + type Target = Foo; + fn deref(&self) -> &Foo { + &self.0 + } +} From a388dd0e47af4de61052a7a5eb6f7f94696bf8a7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 2 Oct 2026 14:24:50 +0200 Subject: [PATCH 04/13] Add new `is_deref[_mut]_trait` methods to simplify code --- src/librustdoc/clean/inline.rs | 5 +++-- src/librustdoc/clean/mod.rs | 2 +- src/librustdoc/clean/types.rs | 4 ++++ src/librustdoc/formats/mod.rs | 9 +++++++++ src/librustdoc/html/render/mod.rs | 13 ++++++------- src/librustdoc/html/render/sidebar.rs | 17 ++++++++--------- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 7f81e29180baf..86d4c8e6599a8 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -598,8 +598,9 @@ pub(crate) fn build_impl( }; let trait_ = associated_trait .map(|t| clean_trait_ref_with_constraints(cx, ty::Binder::dummy(t), ThinVec::new())); - if trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() - && polarity != ty::ImplPolarity::Negative + if trait_ + .as_ref() + .is_some_and(|t| polarity != ty::ImplPolarity::Negative && t.is_deref_trait(tcx)) { super::build_deref_target_impls(cx, &trait_items, ret); } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1b4fb883cbde8..b3b762bb2b771 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -3104,7 +3104,7 @@ fn clean_impl<'tcx>( // If this impl block is a positive implementation of the Deref trait, then we // need to try inlining the target's inherent impl blocks as well. - if trait_.as_ref().is_some_and(|t| tcx.lang_items().deref_trait() == Some(t.def_id())) + if trait_.as_ref().is_some_and(|t| t.is_deref_trait(tcx)) && tcx.impl_polarity(def_id) != ty::ImplPolarity::Negative { build_deref_target_impls(cx, &items, &mut ret); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 224c3851448d3..032749b05c064 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -2169,6 +2169,10 @@ impl Path { } }) } + + pub(crate) fn is_deref_trait(&self, tcx: TyCtxt<'_>) -> bool { + tcx.lang_items().deref_trait() == Some(self.def_id()) + } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 80c110ec07f56..3238484c3a424 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod renderer; pub(crate) use renderer::{FormatRenderer, run_format}; use rustc_hir::def_id::DefId; +use rustc_middle::ty::TyCtxt; use crate::clean::{self, ItemId}; use crate::html::render::Context; @@ -26,6 +27,14 @@ impl Impl { self.inner_impl().trait_.as_ref().map(|t| t.def_id()) } + pub(crate) fn is_deref_trait(&self, tcx: TyCtxt<'_>) -> bool { + self.trait_did() == tcx.lang_items().deref_trait() + } + + pub(crate) fn is_deref_mut_trait(&self, tcx: TyCtxt<'_>) -> bool { + self.trait_did() == tcx.lang_items().deref_mut_trait() + } + /// This function is used to extract a `DefId` to be used as a key for the `Cache::impls` field. /// /// It allows to prevent having duplicated implementations showing up (the biggest issue was diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 8a08dba136bb7..95593fb28eed0 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -1483,11 +1483,11 @@ fn aliased_type_deref_impl<'a>( let mut has_deref_mut = false; for impl_ in impls { - if impl_.trait_did() == tcx.lang_items().deref_trait() { + if impl_.is_deref_trait(tcx) { if !impl_.is_negative_trait_impl() && applies(impl_) { deref_impl = Some(impl_); } - } else if impl_.trait_did() == tcx.lang_items().deref_mut_trait() && applies(impl_) { + } else if impl_.is_deref_mut_trait(tcx) && applies(impl_) { has_deref_mut = true; } } @@ -1622,12 +1622,11 @@ fn render_assoc_items_inner( } } - let deref_impl = trait_impls.iter().find(|t| { - t.trait_did() == cx.tcx().lang_items().deref_trait() && !t.is_negative_trait_impl() - }); + let tcx = cx.tcx(); + let deref_impl = + trait_impls.iter().find(|t| t.is_deref_trait(tcx) && !t.is_negative_trait_impl()); let deref = if let Some(impl_) = deref_impl { - let has_deref_mut = - trait_impls.iter().any(|t| t.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let has_deref_mut = trait_impls.iter().any(|t| t.is_deref_mut_trait(tcx)); Some((*impl_, has_deref_mut)) } else { aliased_type.and_then(|aliased_did| aliased_type_deref_impl(cx, it, aliased_did)) diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 05829dd82e0a9..9ea9161e16371 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -475,6 +475,7 @@ fn sidebar_assoc_items_with_aliased_type<'a>( let mut methods = Vec::new(); let mut used_links = UsedLinks::default(); let mut id_map = IdMap::new(); + let tcx = cx.tcx(); { let used_links_bor = &mut used_links; @@ -510,10 +511,10 @@ fn sidebar_assoc_items_with_aliased_type<'a>( LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), ]; - let deref = if let Some(impl_) = v.iter().find(|i| { - i.trait_did() == cx.tcx().lang_items().deref_trait() && !i.is_negative_trait_impl() - }) { - let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let deref = if let Some(impl_) = + v.iter().find(|i| i.is_deref_trait(tcx) && !i.is_negative_trait_impl()) + { + let deref_mut = v.iter().any(|i| i.is_deref_mut_trait(tcx)); Some((impl_, deref_mut)) } else { aliased_type @@ -568,6 +569,8 @@ fn sidebar_deref_methods<'a>( }) { debug!("found target, real_target: {target:?} {real_target:?}"); + let tcx = cx.tcx(); + if let Some(did) = target.def_id(c) && let Some(type_did) = impl_.inner_impl().for_.def_id(c) && // `impl Deref for S` @@ -629,11 +632,7 @@ fn sidebar_deref_methods<'a>( if let Some(target_did) = target.def_id(c) && let Some(target_impls) = c.impls.get(&target_did) && let Some(target_deref_impl) = target_impls.iter().find(|i| { - i.inner_impl() - .trait_ - .as_ref() - .map(|t| Some(t.def_id()) == tcx.lang_items().deref_trait()) - .unwrap_or(false) + i.inner_impl().trait_.as_ref().map(|t| t.is_deref_trait(tcx)).unwrap_or(false) && !i.is_negative_trait_impl() }) { From 6e5f76072f16b108ae64136274629a73900e43c9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 2 Oct 2026 15:21:21 +0200 Subject: [PATCH 05/13] Remove duplicated code to query `Deref` impls --- src/librustdoc/html/render/mod.rs | 34 ++++++++++++++++++--------- src/librustdoc/html/render/sidebar.rs | 12 +--------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 95593fb28eed0..53c6976d8874a 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -39,7 +39,7 @@ mod sorted_template; mod type_layout; mod write_shared; -use std::borrow::Cow; +use std::borrow::{Borrow, Cow}; use std::cmp::Ordering; use std::collections::{BTreeMap, VecDeque}; use std::fmt::{self, Display as _, Write}; @@ -1494,6 +1494,27 @@ fn aliased_type_deref_impl<'a>( Some((deref_impl?, has_deref_mut)) } +/// Try to get the `Deref` trait `Impl` for `item`. If it cannot be found, it will try to do the +/// same with `aliased_type`. Returns a tuple containing the `Deref` `Impl`, and a bool set to +/// `true` if `DerefMut` is also implemented. +fn get_deref_impls<'a, I: Borrow>( + cx: &'a Context<'_>, + trait_impls: &'a [I], + item: DefId, + aliased_type: Option, +) -> Option<(&'a Impl, bool)> { + let tcx = cx.tcx(); + let mut impls = trait_impls.iter().map(|t| t.borrow()); + if let Some(impl_) = + impls.clone().find(|t| t.is_deref_trait(tcx) && !t.is_negative_trait_impl()) + { + let has_deref_mut = impls.any(|t| t.is_deref_mut_trait(tcx)); + Some((impl_, has_deref_mut)) + } else { + aliased_type.and_then(|aliased_did| aliased_type_deref_impl(cx, item, aliased_did)) + } +} + fn render_assoc_items( cx: &Context<'_>, containing_item: &clean::Item, @@ -1622,16 +1643,7 @@ fn render_assoc_items_inner( } } - let tcx = cx.tcx(); - let deref_impl = - trait_impls.iter().find(|t| t.is_deref_trait(tcx) && !t.is_negative_trait_impl()); - let deref = if let Some(impl_) = deref_impl { - let has_deref_mut = trait_impls.iter().any(|t| t.is_deref_mut_trait(tcx)); - Some((*impl_, has_deref_mut)) - } else { - aliased_type.and_then(|aliased_did| aliased_type_deref_impl(cx, it, aliased_did)) - }; - if let Some((impl_, has_deref_mut)) = deref { + if let Some((impl_, has_deref_mut)) = get_deref_impls(cx, &trait_impls, it, aliased_type) { render_deref_methods(&mut w, cx, impl_, containing_item, has_deref_mut, derefs)?; } diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index 9ea9161e16371..fd9e0520bbb4c 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -511,16 +511,7 @@ fn sidebar_assoc_items_with_aliased_type<'a>( LinkBlock::new(Link::new("implementations", "Methods"), "method", methods), ]; - let deref = if let Some(impl_) = - v.iter().find(|i| i.is_deref_trait(tcx) && !i.is_negative_trait_impl()) - { - let deref_mut = v.iter().any(|i| i.is_deref_mut_trait(tcx)); - Some((impl_, deref_mut)) - } else { - aliased_type - .and_then(|aliased_did| super::aliased_type_deref_impl(cx, item_def_id, aliased_did)) - }; - if let Some((impl_, deref_mut)) = deref { + if let Some((impl_, deref_mut)) = super::get_deref_impls(cx, v, item_def_id, aliased_type) { let mut derefs = DefIdSet::default(); derefs.insert(item_def_id); derefs.extend(aliased_type); @@ -569,7 +560,6 @@ fn sidebar_deref_methods<'a>( }) { debug!("found target, real_target: {target:?} {real_target:?}"); - let tcx = cx.tcx(); if let Some(did) = target.def_id(c) && let Some(type_did) = impl_.inner_impl().for_.def_id(c) && From e1e1dc8792dde26d7b960fddf879539a1a2759c7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 2 Oct 2026 16:24:02 +0200 Subject: [PATCH 06/13] Remove `is_same_generic` test as it cannot work with the new arguments of `Type::is_doc_subtype_of` --- src/librustdoc/clean/types/tests.rs | 13 ------------- src/librustdoc/html/render/sidebar.rs | 1 - 2 files changed, 14 deletions(-) diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index 29a8fd5e2678e..13437e5a008c6 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -69,16 +69,3 @@ fn should_not_trim() { run_test("\t line1 \n\t line2", "line1 \nline2"); run_test(" \tline1 \n \tline2", "line1 \nline2"); } - -#[test] -fn is_same_generic() { - use crate::clean::types::{PrimitiveType, Type}; - use crate::formats::cache::Cache; - create_default_session_globals_then(|| { - let cache = Cache::new(false, false); - let generic = Type::Generic(sym::character('T')); - let unit = Type::Primitive(PrimitiveType::Unit); - assert!(!generic.is_doc_subtype_of(&unit, &cache)); - assert!(unit.is_doc_subtype_of(&generic, &cache)); - }) -} diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index fd9e0520bbb4c..6198b47b95dc9 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -475,7 +475,6 @@ fn sidebar_assoc_items_with_aliased_type<'a>( let mut methods = Vec::new(); let mut used_links = UsedLinks::default(); let mut id_map = IdMap::new(); - let tcx = cx.tcx(); { let used_links_bor = &mut used_links; From b747376628228f6c576731a4082952e814ad4b5e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 5 Oct 2026 22:51:49 +0200 Subject: [PATCH 07/13] Add rustdoc-html test to illustrate known limits to dereferencing to type aliases --- tests/rustdoc-html/deref-to-alias.rs | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/rustdoc-html/deref-to-alias.rs diff --git a/tests/rustdoc-html/deref-to-alias.rs b/tests/rustdoc-html/deref-to-alias.rs new file mode 100644 index 0000000000000..696c7dcf7c29f --- /dev/null +++ b/tests/rustdoc-html/deref-to-alias.rs @@ -0,0 +1,32 @@ +// This test ensures that dereferencing to a type alias will still list +// matching methods of the type alias. + +#![crate_name = "foo"] + +use std::ops::Deref; + +pub struct Foo(T); + +impl Foo { + pub fn get_i32(&self) -> i32 { self.0 } +} + +impl Foo { + pub fn get_u32(&self) -> u32 { self.0 } +} + +pub type X = Foo; + +//@ has 'foo/struct.Bar.html' +// FIXME: Only `get_i32` should be listed, this is a bug. +//@ count - '//*[@id="deref-methods-X-1"]//h4' 2 +//@ has - '//*[@id="deref-methods-X-1"]//h4' 'get_i32' +//@ has - '//*[@id="deref-methods-X-1"]//h4' 'get_u32' +pub struct Bar; + +impl Deref for Bar { + type Target = X; + fn deref(&self) -> &Self::Target { + &X(0) + } +} From 2cab4ca15df3fa9cdb69f190dcca72c1362b7fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beat=20K=C3=BCng?= Date: Wed, 23 Sep 2026 10:23:05 +0200 Subject: [PATCH 08/13] nuttx: use updated pollfd definition Corresponding libc PR: https://github.com/rust-lang/libc/pull/5555 pollfd has some extra internal fields on NuttX, so we use mem::zeroed() to initialze. --- library/std/src/sys/net/connection/socket/unix.rs | 4 +++- library/std/src/sys/pal/unix/mod.rs | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/library/std/src/sys/net/connection/socket/unix.rs b/library/std/src/sys/net/connection/socket/unix.rs index 0f80646449e1f..9c8b9e6f3a2e6 100644 --- a/library/std/src/sys/net/connection/socket/unix.rs +++ b/library/std/src/sys/net/connection/socket/unix.rs @@ -172,7 +172,9 @@ impl Socket { } pub fn poll_connected(&self, timeout: Duration) -> io::Result<()> { - let mut pollfd = libc::pollfd { fd: self.as_raw_fd(), events: libc::POLLOUT, revents: 0 }; + let mut pollfd: libc::pollfd = unsafe { mem::zeroed() }; + pollfd.fd = self.as_raw_fd(); + pollfd.events = libc::POLLOUT; if timeout.as_secs() == 0 && timeout.subsec_nanos() == 0 { return Err(io::Error::ZERO_TIMEOUT); diff --git a/library/std/src/sys/pal/unix/mod.rs b/library/std/src/sys/pal/unix/mod.rs index 292c02f3d1f3e..fbf346a9a4950 100644 --- a/library/std/src/sys/pal/unix/mod.rs +++ b/library/std/src/sys/pal/unix/mod.rs @@ -88,13 +88,16 @@ pub unsafe fn init(argc: isize, argv: *const *const u8, sigpipe: u8) { )))] 'poll: { use crate::sys::io::errno; - let pfds: &mut [_] = &mut [ - libc::pollfd { fd: 0, events: 0, revents: 0 }, - libc::pollfd { fd: 1, events: 0, revents: 0 }, - libc::pollfd { fd: 2, events: 0, revents: 0 }, - ]; - while libc::poll(pfds.as_mut_ptr(), 3, 0) == -1 { + let mut pfds: [libc::pollfd; 3] = crate::mem::zeroed(); + pfds[0].fd = 0; + pfds[0].events = 0; + pfds[1].fd = 1; + pfds[1].events = 0; + pfds[2].fd = 2; + pfds[2].events = 0; + + while libc::poll(pfds.as_mut_ptr(), pfds.len() as libc::nfds_t, 0) == -1 { match errno() { libc::EINTR => continue, #[cfg(target_vendor = "unikraft")] From 2e1670b73658f69dabbd2848615d723f1d4fe9d7 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 5 Nov 2026 20:06:51 +0100 Subject: [PATCH 09/13] Strenghten `deref` and `deref_mut` checks to ensure we don't compare `None == None` --- src/librustdoc/formats/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index 3238484c3a424..7cfd0f83cd2f0 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -28,11 +28,11 @@ impl Impl { } pub(crate) fn is_deref_trait(&self, tcx: TyCtxt<'_>) -> bool { - self.trait_did() == tcx.lang_items().deref_trait() + self.trait_did().is_some_and(|did| Some(did) == tcx.lang_items().deref_trait()) } pub(crate) fn is_deref_mut_trait(&self, tcx: TyCtxt<'_>) -> bool { - self.trait_did() == tcx.lang_items().deref_mut_trait() + self.trait_did().is_some_and(|did| Some(did) == tcx.lang_items().deref_mut_trait()) } /// This function is used to extract a `DefId` to be used as a key for the `Cache::impls` field. From dc5ab2045c4164eb2ee3fbf01fe3abcabf79b441 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 5 Nov 2026 23:39:47 +0100 Subject: [PATCH 10/13] Add multi-level `Deref` test --- .../deref/deref-methods-aliased-type.rs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/rustdoc-html/deref/deref-methods-aliased-type.rs b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs index 5e0c9a1d4e769..35d3a7eb2433e 100644 --- a/tests/rustdoc-html/deref/deref-methods-aliased-type.rs +++ b/tests/rustdoc-html/deref/deref-methods-aliased-type.rs @@ -85,3 +85,30 @@ pub type Word = Wrapper; //@ count - '//*[@class="section-header"]' 1 //@ has - '//*[@class="section-header"]' 'Aliased Type' pub type Dword = Wrapper; + +pub struct Foo(T); + +impl Foo { + pub fn tada(&self) {} +} + +impl Deref for Foo { + type Target = T; + fn deref(&self) -> &T { + &self.0 + } +} + +// Checks that traversal `Deref` is working. +//@ has 'foo/struct.FooBar.html' +//@ has - '//*[@id="deref-methods-Foo%3CFoo%3CT%3E%3E-1"]//*[@class="code-header"]' \ +// 'pub fn tada(&self)' +//@ has - '//*[@id="deref-methods-Foo%3CFoo%3CT%3E%3E"]' 'Methods from Deref>>' +pub struct FooBar(Foo>); + +impl Deref for FooBar { + type Target = Foo>; + fn deref(&self) -> &Foo> { + &self.0 + } +} From 4efd16754f76122bfef88dadf4ff22430418f82a Mon Sep 17 00:00:00 2001 From: cezarbbb Date: Mon, 26 Oct 2026 10:34:36 +0800 Subject: [PATCH 11/13] Introduce `OptimizationObjective` and fix the entry shim's size attributes --- compiler/rustc_codegen_llvm/src/attributes.rs | 2 ++ compiler/rustc_codegen_llvm/src/back/write.rs | 19 +++++----- compiler/rustc_codegen_llvm/src/context.rs | 5 +++ compiler/rustc_codegen_llvm/src/intrinsic.rs | 1 + compiler/rustc_codegen_llvm/src/llvm/mod.rs | 9 ----- compiler/rustc_codegen_ssa/src/back/linker.rs | 7 ++-- compiler/rustc_codegen_ssa/src/back/write.rs | 18 ++++------ compiler/rustc_codegen_ssa/src/base.rs | 35 +++++++------------ compiler/rustc_codegen_ssa/src/traits/misc.rs | 3 ++ compiler/rustc_session/src/config.rs | 34 +++++++++++++++--- compiler/rustc_session/src/session.rs | 5 +++ .../codegen-llvm/loop-vectorize-in-opt-sz.rs | 23 ++++++++++++ tests/codegen-llvm/merge-functions.rs | 3 +- tests/codegen-llvm/opt-size-attr-shim.rs | 30 ++++++++++++++++ tests/codegen-llvm/opt-size-attr.rs | 29 +++++++++++++++ tests/codegen-llvm/unroll-in-opt-sz.rs | 31 ++++++++++++++++ 16 files changed, 193 insertions(+), 61 deletions(-) create mode 100644 tests/codegen-llvm/loop-vectorize-in-opt-sz.rs create mode 100644 tests/codegen-llvm/opt-size-attr-shim.rs create mode 100644 tests/codegen-llvm/opt-size-attr.rs create mode 100644 tests/codegen-llvm/unroll-in-opt-sz.rs diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs index bdbd43de07a53..d74c6dd68564b 100644 --- a/compiler/rustc_codegen_llvm/src/attributes.rs +++ b/compiler/rustc_codegen_llvm/src/attributes.rs @@ -478,6 +478,8 @@ pub(crate) fn default_optimisation_attrs<'ll>( sess: &Session, ) -> SmallVec<[&'ll Attribute; 2]> { let mut attrs = SmallVec::new(); + // Either attribute satisfies LLVM's combined size predicate, but the software pipeliner reads + // `optsize` alone, so a `z` build needs both. match sess.opts.optimize { OptLevel::Size => { attrs.push(llvm::AttributeKind::OptimizeForSize.create_attr(cx.llcx)); diff --git a/compiler/rustc_codegen_llvm/src/back/write.rs b/compiler/rustc_codegen_llvm/src/back/write.rs index 693d8e6adb1c3..e43b9ba6359ed 100644 --- a/compiler/rustc_codegen_llvm/src/back/write.rs +++ b/compiler/rustc_codegen_llvm/src/back/write.rs @@ -119,15 +119,15 @@ pub(crate) fn create_target_machine(tcx: TyCtxt<'_>, mod_name: &str) -> OwnedTar target_machine_factory(tcx.sess, tcx.backend_optimization_level(()))(tcx.dcx(), config) } -fn to_llvm_opt_settings(cfg: config::OptLevel) -> (llvm::CodeGenOptLevel, llvm::CodeGenOptSize) { +// `s` and `z` are the same codegen level; what tells LLVM that they are size builds is the +// per-function attribute, not this setting. +fn to_llvm_opt_level(cfg: config::OptLevel) -> llvm::CodeGenOptLevel { use self::config::OptLevel::*; match cfg { - No => (llvm::CodeGenOptLevel::None, llvm::CodeGenOptSizeNone), - Less => (llvm::CodeGenOptLevel::Less, llvm::CodeGenOptSizeNone), - More => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeNone), - Aggressive => (llvm::CodeGenOptLevel::Aggressive, llvm::CodeGenOptSizeNone), - Size => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeDefault), - SizeMin => (llvm::CodeGenOptLevel::Default, llvm::CodeGenOptSizeAggressive), + No => llvm::CodeGenOptLevel::None, + Less => llvm::CodeGenOptLevel::Less, + More | Size | SizeMin => llvm::CodeGenOptLevel::Default, + Aggressive => llvm::CodeGenOptLevel::Aggressive, } } @@ -184,7 +184,7 @@ pub(crate) fn target_machine_factory( let reloc_model = to_llvm_relocation_model(sess.relocation_model()); - let (opt_level, _) = to_llvm_opt_settings(optlvl); + let opt_level = to_llvm_opt_level(optlvl); let float_abi = to_llvm_float_abi(sess.target.llvm_floatabi); let ffunction_sections = @@ -594,8 +594,7 @@ pub(crate) unsafe fn llvm_optimize<'ll>( vectorize_slp = false; vectorize_loop = false; } else { - unroll_loops = - opt_level != config::OptLevel::Size && opt_level != config::OptLevel::SizeMin; + unroll_loops = opt_level.objective() != config::OptimizationObjective::Size; merge_functions = config.merge_functions; vectorize_slp = config.vectorize_slp; vectorize_loop = config.vectorize_loop; diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 40d5e08578faf..6ba3a82de659f 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -1071,6 +1071,11 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &attrs); } + fn apply_default_optimisation_attrs(&self, llfn: &'ll Value) { + let attrs = attributes::default_optimisation_attrs(self, self.sess()); + attributes::apply_to_llfn(llfn, llvm::AttributePlace::Function, &attrs); + } + fn declare_c_main(&self, fn_type: Self::Type) -> Option { let entry_name = self.sess().target.entry_name.as_ref(); if self.get_declared_value(entry_name).is_none() { diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index 594f4cbcf759d..38af3011b47e7 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -1707,6 +1707,7 @@ fn gen_fn<'a, 'll, 'tcx>( let llfn = cx.declare_fn(name, fn_abi, None); cx.set_frame_pointer_type(llfn); cx.apply_target_cpu_attr(llfn); + cx.apply_default_optimisation_attrs(llfn); // FIXME(eddyb) find a nicer way to do this. llvm::set_linkage(llfn, llvm::Linkage::InternalLinkage); let llbb = Builder::append_block(cx, llfn, "entry-block"); diff --git a/compiler/rustc_codegen_llvm/src/llvm/mod.rs b/compiler/rustc_codegen_llvm/src/llvm/mod.rs index f9863da230dfb..e77fed0913ec2 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/mod.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/mod.rs @@ -10,7 +10,6 @@ use rustc_abi::{AddressSpace, Align, Size, WrappingRange}; use rustc_llvm::RustString; pub(crate) use self::CallConv::*; -pub(crate) use self::CodeGenOptSize::*; pub(crate) use self::conversions::*; pub(crate) use self::ffi::*; pub(crate) use self::metadata_kind::*; @@ -195,14 +194,6 @@ impl AttributePlace { } } -#[derive(Copy, Clone, PartialEq)] -#[repr(C)] -pub(crate) enum CodeGenOptSize { - CodeGenOptSizeNone = 0, - CodeGenOptSizeDefault = 1, - CodeGenOptSizeAggressive = 2, -} - pub(crate) fn SetInstructionCallConv(instr: &Value, cc: CallConv) { unsafe { LLVMSetInstructionCallConv(instr, cc as c_uint); diff --git a/compiler/rustc_codegen_ssa/src/back/linker.rs b/compiler/rustc_codegen_ssa/src/back/linker.rs index ffb9130ed1a08..b92e79c8f43ec 100644 --- a/compiler/rustc_codegen_ssa/src/back/linker.rs +++ b/compiler/rustc_codegen_ssa/src/back/linker.rs @@ -407,6 +407,7 @@ impl<'a> GccLinker<'a> { self.link_arg(&arg); } + // The plugin has no distinct size setting: `s` and `z` both map to `O2`, as `More` does. let opt_level = match self.sess.opts.optimize { config::OptLevel::No => "O0", config::OptLevel::Less => "O1", @@ -698,9 +699,7 @@ impl<'a> Linker for GccLinker<'a> { // GNU-style linkers support optimization with -O. GNU ld doesn't // need a numeric argument, but other linkers do. - if self.sess.opts.optimize == config::OptLevel::More - || self.sess.opts.optimize == config::OptLevel::Aggressive - { + if self.sess.optimization_objective() == config::OptimizationObjective::Speed { self.link_arg("-O1"); } } @@ -1021,7 +1020,7 @@ impl<'a> Linker for MsvcLinker<'a> { // MSVC's ICF (Identical COMDAT Folding) link optimization is // slow for Rust and thus we disable it by default when not in // optimization build. - if self.sess.opts.optimize != config::OptLevel::No { + if self.sess.optimization_objective() != config::OptimizationObjective::None { self.link_arg("/OPT:REF,ICF"); } else { // It is necessary to specify NOICF here, because /OPT:REF diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 44958c376a46a..406e5a5b04c61 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -23,8 +23,8 @@ use rustc_metadata::fs::copy_to_stdout; use rustc_middle::dep_graph::{WorkProduct, WorkProductMap}; use rustc_middle::ty::TyCtxt; use rustc_session::config::{ - self, JobsMode, Lto, OptLevel, OutFileName, OutputFilenames, OutputType, Passes, - SwitchWithOptPath, + self, JobsMode, Lto, OptLevel, OptimizationObjective, OutFileName, OutputFilenames, OutputType, + Passes, SwitchWithOptPath, }; use rustc_session::{BorrowedIncrCompSession, IncrCompSession, Session}; use rustc_span::source_map::SourceMap; @@ -231,8 +231,7 @@ impl ModuleConfig { // Copy what clang does by turning on loop vectorization at O2 and // slp vectorization at O3. vectorize_loop: !sess.opts.cg.no_vectorize_loops - && (sess.opts.optimize == config::OptLevel::More - || sess.opts.optimize == config::OptLevel::Aggressive), + && sess.optimization_objective() == OptimizationObjective::Speed, vectorize_slp: !sess.opts.cg.no_vectorize_slp && sess.opts.optimize == config::OptLevel::Aggressive, @@ -247,13 +246,10 @@ impl ModuleConfig { // O2 and O3) since it can be useful for reducing code size. merge_functions: match sess.merge_functions() { MergeFunctions::Disabled => false, - MergeFunctions::Trampolines | MergeFunctions::Aliases => { - use config::OptLevel::*; - match sess.opts.optimize { - Aggressive | More | SizeMin | Size => true, - Less | No => false, - } - } + MergeFunctions::Trampolines | MergeFunctions::Aliases => matches!( + sess.optimization_objective(), + OptimizationObjective::Speed | OptimizationObjective::Size + ), }, emit_lifetime_markers: sess.emit_lifetime_markers(), diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 1f820a17195bb..ae35dcfe42e60 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -555,6 +555,8 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // `main` should respect same config for frame pointer elimination as rest of code cx.set_frame_pointer_type(llfn); cx.apply_target_cpu_attr(llfn); + // The shim has no item behind it, so it does not go through the per-item attribute path. + cx.apply_default_optimisation_attrs(llfn); let llbb = Bx::append_block(cx, llfn, "top"); let mut bx = Bx::build(cx, llbb); @@ -1217,23 +1219,16 @@ impl CrateInfo { pub(crate) fn provide(providers: &mut Providers) { providers.backend_optimization_level = |tcx, cratenum| { - let for_speed = match tcx.sess.opts.optimize { - // If globally no optimisation is done, #[optimize] has no effect. - // - // This is done because if we ended up "upgrading" to `-O2` here, we’d populate the - // pass manager and it is likely that some module-wide passes (such as inliner or - // cross-function constant propagation) would ignore the `optnone` annotation we put - // on the functions, thus necessarily involving these functions into optimisations. - config::OptLevel::No => return config::OptLevel::No, - // If globally optimise-speed is already specified, just use that level. - config::OptLevel::Less => return config::OptLevel::Less, - config::OptLevel::More => return config::OptLevel::More, - config::OptLevel::Aggressive => return config::OptLevel::Aggressive, - // If globally optimize-for-size has been requested, use -O2 instead (if optimize(size) - // are present). - config::OptLevel::Size => config::OptLevel::More, - config::OptLevel::SizeMin => config::OptLevel::More, - }; + let global = tcx.sess.opts.optimize; + + // Nothing to promote outside a size build. At `-Copt-level=0` the upgrade would populate + // the pass manager, and its module-wide passes ignore the `optnone` annotation. + if global.objective() != config::OptimizationObjective::Size { + return global; + } + + // A speed-marked body in a size build needs a speed level, and `-O2` is the lowest one. + let for_speed = config::OptLevel::More; let defids = tcx.collect_and_partition_mono_items(cratenum).all_mono_items; @@ -1242,11 +1237,7 @@ pub(crate) fn provide(providers: &mut Providers) { matches!(optimize, OptimizeAttr::Speed) }); - if any_for_speed { - return for_speed; - } - - tcx.sess.opts.optimize + if any_for_speed { for_speed } else { global } }; } diff --git a/compiler/rustc_codegen_ssa/src/traits/misc.rs b/compiler/rustc_codegen_ssa/src/traits/misc.rs index add7128a2974b..7e48e86290ee8 100644 --- a/compiler/rustc_codegen_ssa/src/traits/misc.rs +++ b/compiler/rustc_codegen_ssa/src/traits/misc.rs @@ -28,6 +28,9 @@ pub trait MiscCodegenMethods<'tcx>: BackendTypes { fn sess(&self) -> &Session; fn set_frame_pointer_type(&self, llfn: Self::Function); fn apply_target_cpu_attr(&self, llfn: Self::Function); + /// Applies the compilation's optimization attributes to a function that no item produced, + /// such as the C entry shim. + fn apply_default_optimisation_attrs(&self, _llfn: Self::Function) {} /// Declares the extern "C" main function for the entry point. Returns None if the symbol /// already exists. fn declare_c_main(&self, fn_type: Self::FunctionSignature) -> Option; diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index b3d30997aca2a..2665cb92fc8d0 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -126,6 +126,33 @@ impl OptLevel { _ => 2, } } + + /// Maps a general opt-level onto the objective it is trying to achieve. + pub fn objective(&self) -> OptimizationObjective { + match self { + OptLevel::No => OptimizationObjective::None, + OptLevel::Less => OptimizationObjective::Balanced, + OptLevel::More | OptLevel::Aggressive => OptimizationObjective::Speed, + OptLevel::Size | OptLevel::SizeMin => OptimizationObjective::Size, + } + } +} + +/// What a compilation, or a single item, is being asked to optimize *for*. +/// +/// Unlike [`OptLevel`], speed and size are alternatives here rather than degrees of the same +/// thing. Sites that map a level onto an LLVM or linker flag, where `s` and `z` produce different +/// flags, still match on [`OptLevel`]. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum OptimizationObjective { + /// Nothing is optimized, so there is no objective (`-Copt-level=0`). + None, + /// Optimize, without a strong preference for speed over size (`-Copt-level=1`). + Balanced, + /// Favor runtime speed over binary size (`-Copt-level=2`/`3`, `#[optimize(speed)]`). + Speed, + /// Favor binary size over runtime speed (`-Copt-level=s`/`z`, `#[optimize(size)]`). + Size, } /// This is what the `LtoCli` values get mapped to after resolving defaults and @@ -1572,10 +1599,9 @@ impl Options { pub fn share_generics(&self) -> bool { match self.unstable_opts.share_generics { Some(setting) => setting, - None => match self.optimize { - OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true, - OptLevel::More | OptLevel::Aggressive => false, - }, + // Sharing instantiations means fewer copies, at the cost of leaving them opaque to + // speed optimizations. That is a good trade unless speed is what is asked for. + None => self.optimize.objective() != OptimizationObjective::Speed, } } diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index b69c26639e7e8..9bf2de0466233 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -988,6 +988,11 @@ impl Session { self.opts.unstable_opts.mir_opt_level.unwrap_or_else(|| self.opts.optimize.mir_opt_level()) } + /// What this compilation optimizes for; see [`config::OptimizationObjective`]. + pub fn optimization_objective(&self) -> config::OptimizationObjective { + self.opts.optimize.objective() + } + /// Calculates the flavor of LTO to use for this compilation. pub fn lto(&self) -> config::Lto { // If our target has codegen requirements ignore the command line diff --git a/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs b/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs new file mode 100644 index 0000000000000..f26a558f17ea4 --- /dev/null +++ b/tests/codegen-llvm/loop-vectorize-in-opt-sz.rs @@ -0,0 +1,23 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z +//@ only-x86_64 + +// Loop vectorization is enabled only for the speed objective, so both `-Copt-level=s` and `z` leave +// it off. The loop below is long enough that LLVM at `-O2` does vectorize it. + +#![crate_type = "lib"] + +// CHECK-LABEL: @scale_add( +// O2: fmul <4 x float> +// O2: fadd <4 x float> +// O2: store <4 x float> +// S-NOT: <4 x float> +// Z-NOT: <4 x float> +#[no_mangle] +pub fn scale_add(a: &[f32], b: &[f32], c: &mut [f32]) { + for i in 0..64 { + c[i] = a[i] * b[i] + 3.0; + } +} diff --git a/tests/codegen-llvm/merge-functions.rs b/tests/codegen-llvm/merge-functions.rs index b9d3727ce112e..8c15791c5ae74 100644 --- a/tests/codegen-llvm/merge-functions.rs +++ b/tests/codegen-llvm/merge-functions.rs @@ -1,5 +1,6 @@ -//@ revisions: O Os +//@ revisions: O Os Oz //@[Os] compile-flags: -Copt-level=s +//@[Oz] compile-flags: -Copt-level=z //@[O] compile-flags: -Copt-level=3 #![crate_type = "lib"] diff --git a/tests/codegen-llvm/opt-size-attr-shim.rs b/tests/codegen-llvm/opt-size-attr-shim.rs new file mode 100644 index 0000000000000..a82a3219ca5b1 --- /dev/null +++ b/tests/codegen-llvm/opt-size-attr-shim.rs @@ -0,0 +1,30 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z +//@ ignore-wasi wasi codegens the main symbol differently + +// The C entry shim has no item behind it, so it does not go through the per-item attribute path. +// It still has to carry the size attributes, or `main` is the one definition in a size build that +// LLVM optimizes for speed. + +#![crate_type = "bin"] + +// CHECK-LABEL: define {{.*}} @main( +// CHECK-SAME: [[MAIN_ATTRS:#[0-9]+]] + +// O2: attributes [[MAIN_ATTRS]] = +// O2-NOT: optsize +// O2-NOT: minsize + +// S: attributes [[MAIN_ATTRS]] = +// S-NOT: minsize +// S-SAME: optsize + +// Z: attributes [[MAIN_ATTRS]] = +// Z-SAME: minsize +// Z-SAME: optsize + +fn main() { + std::hint::black_box(0); +} diff --git a/tests/codegen-llvm/opt-size-attr.rs b/tests/codegen-llvm/opt-size-attr.rs new file mode 100644 index 0000000000000..7784e8d1c8e47 --- /dev/null +++ b/tests/codegen-llvm/opt-size-attr.rs @@ -0,0 +1,29 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z + +// `s` gives `optsize` while `z` gives both `minsize` and `optsize`. This is the only coverage of +// the `-Copt-level` path; `optimize-attr-1.rs` reaches these via `#[optimize(size)]`. + +#![crate_type = "lib"] + +// CHECK-LABEL: @f( +// CHECK-SAME: [[F_ATTRS:#[0-9]+]] + +// O2: attributes [[F_ATTRS]] = +// O2-NOT: optsize +// O2-NOT: minsize + +// S: attributes [[F_ATTRS]] = +// S-NOT: minsize +// S-SAME: optsize + +// Z: attributes [[F_ATTRS]] = +// Z-SAME: minsize +// Z-SAME: optsize + +#[no_mangle] +pub fn f(x: u32) -> u32 { + x + 1 +} diff --git a/tests/codegen-llvm/unroll-in-opt-sz.rs b/tests/codegen-llvm/unroll-in-opt-sz.rs new file mode 100644 index 0000000000000..a76c8b3fba51c --- /dev/null +++ b/tests/codegen-llvm/unroll-in-opt-sz.rs @@ -0,0 +1,31 @@ +//@ revisions: O2 S Z +//@[O2] compile-flags: -Copt-level=2 +//@[S] compile-flags: -Copt-level=s +//@[Z] compile-flags: -Copt-level=z + +// `-Copt-level=s`/`z` pass `PTO.LoopUnrolling = false`, so LLVM's unroller runs `OnlyWhenForced`. +// The O2 side is asserted too, so disabling unrolling everywhere cannot pass. Calls are counted +// because this knob emits no `llvm.loop.unroll.*` metadata. + +#![crate_type = "lib"] + +unsafe extern "C" { + fn side_effect(i: u32); +} + +// O2-LABEL: @four_iters( +// O2-COUNT-4: tail call void @side_effect +// S-LABEL: @four_iters( +// S: phi i32 +// S: tail call void @side_effect +// S-NOT: tail call void @side_effect +// Z-LABEL: @four_iters( +// Z: phi i32 +// Z: tail call void @side_effect +// Z-NOT: tail call void @side_effect +#[no_mangle] +pub fn four_iters() { + for i in 0..4u32 { + unsafe { side_effect(i) } + } +} From 45bea6c327a782d35ef87b6ef791df5f946decfd Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Thu, 5 Nov 2026 20:22:19 +0600 Subject: [PATCH 12/13] add tests for -Zcrate-attr ordering rfc 3791 says crate attributes from the command line apply before the attributes in the crate root. ordering-cli checks that several -Zcrate-attr flags apply in command line order. ordering-source records the current behaviour where the command line deny overrides the source warn, which is the wrong way round. --- tests/ui/attributes/z-crate-attr/ordering-cli.rs | 6 ++++++ .../attributes/z-crate-attr/ordering-cli.stderr | 15 +++++++++++++++ .../ui/attributes/z-crate-attr/ordering-source.rs | 8 ++++++++ .../z-crate-attr/ordering-source.stderr | 15 +++++++++++++++ 4 files changed, 44 insertions(+) create mode 100644 tests/ui/attributes/z-crate-attr/ordering-cli.rs create mode 100644 tests/ui/attributes/z-crate-attr/ordering-cli.stderr create mode 100644 tests/ui/attributes/z-crate-attr/ordering-source.rs create mode 100644 tests/ui/attributes/z-crate-attr/ordering-source.stderr diff --git a/tests/ui/attributes/z-crate-attr/ordering-cli.rs b/tests/ui/attributes/z-crate-attr/ordering-cli.rs new file mode 100644 index 0000000000000..877a25939b9f3 --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-cli.rs @@ -0,0 +1,6 @@ +// Multiple `-Zcrate-attr` flags apply in command-line order, so the later `deny` wins. +//@ compile-flags: -Zcrate-attr=warn(unused) -Zcrate-attr=deny(unused) + +fn foo() {} //~ ERROR function `foo` is never used + +fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-cli.stderr b/tests/ui/attributes/z-crate-attr/ordering-cli.stderr new file mode 100644 index 0000000000000..84c47716e62fb --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-cli.stderr @@ -0,0 +1,15 @@ +error: function `foo` is never used + --> $DIR/ordering-cli.rs:4:4 + | +LL | fn foo() {} + | ^^^ + | +note: the lint level is defined here + --> :1:9 + | +LL | #![deny(unused)] + | ^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.rs b/tests/ui/attributes/z-crate-attr/ordering-source.rs new file mode 100644 index 0000000000000..d4a248a6be5ca --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-source.rs @@ -0,0 +1,8 @@ +// RFC 3791: `-Zcrate-attr` applies before the crate's own attributes, so `warn` here wins. +//@ compile-flags: -Zcrate-attr=deny(unused) + +#![warn(unused)] + +fn foo() {} //~ ERROR function `foo` is never used + +fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.stderr b/tests/ui/attributes/z-crate-attr/ordering-source.stderr new file mode 100644 index 0000000000000..75af32fc695cd --- /dev/null +++ b/tests/ui/attributes/z-crate-attr/ordering-source.stderr @@ -0,0 +1,15 @@ +error: function `foo` is never used + --> $DIR/ordering-source.rs:6:4 + | +LL | fn foo() {} + | ^^^ + | +note: the lint level is defined here + --> :1:9 + | +LL | #![deny(unused)] + | ^^^^^^ + = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + +error: aborting due to 1 previous error + From 851c9e042bf06381d712e4d79fe6fac826b1d8d3 Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Thu, 5 Nov 2026 07:52:35 +0600 Subject: [PATCH 13/13] apply -Zcrate-attr attributes before the crate attributes cmdline attributes were pushed after the attributes parsed from source, so a -Zcrate-attr lint level overrode the same lint set in the crate root. rfc 3791 says they apply before the source attributes. they are now inserted at the front of the crate attributes in command line order. --- .../rustc_builtin_macros/src/cmdline_attrs.rs | 15 +++++++-------- .../ui/attributes/z-crate-attr/ordering-source.rs | 3 ++- .../z-crate-attr/ordering-source.stderr | 12 ++++++------ 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs index b294328b0bb17..b0045c359160d 100644 --- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs +++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs @@ -8,7 +8,7 @@ use rustc_session::parse::ParseSess; use rustc_span::FileName; pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) { - for raw_attr in attrs { + let injected = attrs.iter().filter_map(|raw_attr| { let source = format!("#![{raw_attr}]"); let parse = || -> Result>> { let tokens = source_str_to_stream( @@ -22,16 +22,15 @@ pub fn inject(krate: &mut ast::Crate, psess: &ParseSess, attrs: &[String]) { }) .map_err(|e| vec![e]) }; - let meta = match parse() { - Ok(meta) => meta, + match parse() { + Ok(meta) => Some(meta), Err(errs) => { for err in errs { err.emit(); } - continue; + None } - }; - - krate.attrs.push(meta); - } + } + }); + krate.attrs.splice(0..0, injected); } diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.rs b/tests/ui/attributes/z-crate-attr/ordering-source.rs index d4a248a6be5ca..914851d3bb055 100644 --- a/tests/ui/attributes/z-crate-attr/ordering-source.rs +++ b/tests/ui/attributes/z-crate-attr/ordering-source.rs @@ -1,8 +1,9 @@ // RFC 3791: `-Zcrate-attr` applies before the crate's own attributes, so `warn` here wins. +//@ check-pass //@ compile-flags: -Zcrate-attr=deny(unused) #![warn(unused)] -fn foo() {} //~ ERROR function `foo` is never used +fn foo() {} //~ WARN function `foo` is never used fn main() {} diff --git a/tests/ui/attributes/z-crate-attr/ordering-source.stderr b/tests/ui/attributes/z-crate-attr/ordering-source.stderr index 75af32fc695cd..66cf78738bf7c 100644 --- a/tests/ui/attributes/z-crate-attr/ordering-source.stderr +++ b/tests/ui/attributes/z-crate-attr/ordering-source.stderr @@ -1,15 +1,15 @@ -error: function `foo` is never used - --> $DIR/ordering-source.rs:6:4 +warning: function `foo` is never used + --> $DIR/ordering-source.rs:7:4 | LL | fn foo() {} | ^^^ | note: the lint level is defined here - --> :1:9 + --> $DIR/ordering-source.rs:5:9 | -LL | #![deny(unused)] +LL | #![warn(unused)] | ^^^^^^ - = note: `#[deny(dead_code)]` implied by `#[deny(unused)]` + = note: `#[warn(dead_code)]` implied by `#[warn(unused)]` -error: aborting due to 1 previous error +warning: 1 warning emitted