From c18cd774b360b25507c09e84bdb80b1bffc619c5 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 23 Oct 2025 12:59:29 +0200 Subject: [PATCH 1/7] [rustdoc] Simplify module rendering and HTML tags handling --- src/librustdoc/html/render/print_item.rs | 288 ++++++++++++----------- 1 file changed, 145 insertions(+), 143 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index adfc7481c73ae..b9aac4c7c51c1 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -307,8 +307,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i fmt::from_fn(|w| { write!(w, "{}", document(cx, item, None, HeadingOffset::H2))?; - let mut not_stripped_items = - items.iter().filter(|i| !i.is_stripped()).enumerate().collect::>(); + let mut not_stripped_items: FxHashMap> = + FxHashMap::default(); + + for (index, item) in items.iter().filter(|i| !i.is_stripped()).enumerate() { + not_stripped_items.entry(item.type_()).or_default().push((index, item)); + } // the order of item types in the listing fn reorder(ty: ItemType) -> u8 { @@ -331,11 +335,6 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i } fn cmp(i1: &clean::Item, i2: &clean::Item, tcx: TyCtxt<'_>) -> Ordering { - let rty1 = reorder(i1.type_()); - let rty2 = reorder(i2.type_()); - if rty1 != rty2 { - return rty1.cmp(&rty2); - } let is_stable1 = i1.stability(tcx).as_ref().map(|s| s.level.is_stable()).unwrap_or(true); let is_stable2 = @@ -356,8 +355,12 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let tcx = cx.tcx(); match cx.shared.module_sorting { - ModuleSorting::Alphabetical => { - not_stripped_items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); + ModuleSorting::Alphabetical => + { + #[allow(rustc::potential_query_instability)] + for items in not_stripped_items.values_mut() { + items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); + } } ModuleSorting::DeclarationOrder => {} } @@ -380,155 +383,154 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i // can be identical even if the elements are different (mostly in imports). // So in case this is an import, we keep everything by adding a "unique id" // (which is the position in the vector). - not_stripped_items.dedup_by_key(|(idx, i)| { - ( - i.item_id, - if i.name.is_some() { Some(full_path(cx, i)) } else { None }, - i.type_(), - if i.is_import() { *idx } else { 0 }, - ) - }); + #[allow(rustc::potential_query_instability)] + for items in not_stripped_items.values_mut() { + items.dedup_by_key(|(idx, i)| { + ( + i.item_id, + if i.name.is_some() { Some(full_path(cx, i)) } else { None }, + i.type_(), + if i.is_import() { *idx } else { 0 }, + ) + }); + } debug!("{not_stripped_items:?}"); - let mut last_section = None; - for (_, myitem) in ¬_stripped_items { - let my_section = item_ty_to_section(myitem.type_()); - if Some(my_section) != last_section { - if last_section.is_some() { - w.write_str(ITEM_TABLE_CLOSE)?; - } - last_section = Some(my_section); - let section_id = my_section.id(); - let tag = - if section_id == "reexports" { REEXPORTS_TABLE_OPEN } else { ITEM_TABLE_OPEN }; - write!( - w, - "{}", - write_section_heading(my_section.name(), &cx.derive_id(section_id), None, tag) - )?; - } + #[allow(rustc::potential_query_instability)] + let mut types = not_stripped_items.keys().copied().collect::>(); + types.sort_unstable_by(|a, b| reorder(*a).cmp(&reorder(*b))); - match myitem.kind { - clean::ExternCrateItem { ref src } => { - use crate::html::format::print_anchor; - - match *src { - Some(src) => { - write!( - w, - "
{}extern crate {} as {};", - visibility_print_with_space(myitem, cx), - print_anchor(myitem.item_id.expect_def_id(), src, cx), - EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) - )?; - } - None => { - write!( - w, - "
{}extern crate {};", - visibility_print_with_space(myitem, cx), - print_anchor( - myitem.item_id.expect_def_id(), - myitem.name.unwrap(), - cx - ) - )?; - } - } - w.write_str("
")?; - } + for type_ in types { + let my_section = item_ty_to_section(type_); + let tag = if my_section == super::ItemSection::Reexports { + REEXPORTS_TABLE_OPEN + } else { + ITEM_TABLE_OPEN + }; + write!( + w, + "{}", + write_section_heading(my_section.name(), &cx.derive_id(my_section.id()), None, tag) + )?; - clean::ImportItem(ref import) => { - let stab_tags = import.source.did.map_or_else(String::new, |import_def_id| { - print_extra_info_tags(tcx, myitem, item, Some(import_def_id)).to_string() - }); + for (_, myitem) in ¬_stripped_items[&type_] { + match myitem.kind { + clean::ExternCrateItem { ref src } => { + use crate::html::format::print_anchor; - let id = match import.kind { - clean::ImportKind::Simple(s) => { - format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) + match *src { + Some(src) => { + write!( + w, + "
{}extern crate {} as {};", + visibility_print_with_space(myitem, cx), + print_anchor(myitem.item_id.expect_def_id(), src, cx), + EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()) + )?; + } + None => { + write!( + w, + "
{}extern crate {};", + visibility_print_with_space(myitem, cx), + print_anchor( + myitem.item_id.expect_def_id(), + myitem.name.unwrap(), + cx + ) + )?; + } } - clean::ImportKind::Glob => String::new(), - }; - write!( - w, - "\ - " - )?; - render_attributes_in_code(w, myitem, "", cx); - write!( - w, - "{vis}{imp}{stab_tags}\ -
", - vis = visibility_print_with_space(myitem, cx), - imp = import.print(cx) - )?; - } - - _ => { - if myitem.name.is_none() { - continue; } - - let unsafety_flag = match myitem.kind { - clean::FunctionItem(_) | clean::ForeignFunctionItem(..) - if myitem.fn_header(tcx).unwrap().safety - == hir::HeaderSafety::Normal(hir::Safety::Unsafe) => - { - "" - } - clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { - "" - } - _ => "", - }; - - let visibility_and_hidden = match myitem.visibility(tcx) { - Some(ty::Visibility::Restricted(_)) => { - if myitem.is_doc_hidden() { - // Don't separate with a space when there are two of them - " 🔒👻 " - } else { - " 🔒 " + clean::ImportItem(ref import) => { + let stab_tags = + import.source.did.map_or_else(String::new, |import_def_id| { + print_extra_info_tags(tcx, myitem, item, Some(import_def_id)) + .to_string() + }); + let id = match import.kind { + clean::ImportKind::Simple(s) => { + format!(" id=\"{}\"", cx.derive_id(format!("reexport.{s}"))) } + clean::ImportKind::Glob => String::new(), + }; + write!( + w, + "\ + " + )?; + render_attributes_in_code(w, myitem, "", cx); + write!( + w, + "{vis}{imp}{stab_tags}\ + ", + vis = visibility_print_with_space(myitem, cx), + imp = import.print(cx) + )?; + } + _ => { + if myitem.name.is_none() { + continue; } - _ if myitem.is_doc_hidden() => { - " 👻 " - } - _ => "", - }; - let docs = - MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)).into_string(); - let (docs_before, docs_after) = - if docs.is_empty() { ("", "") } else { ("
", "
") }; - write!( - w, - "
\ - \ - {name}\ - \ - {visibility_and_hidden}\ - {unsafety_flag}\ - {stab_tags}\ -
\ - {docs_before}{docs}{docs_after}", - name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), - visibility_and_hidden = visibility_and_hidden, - stab_tags = print_extra_info_tags(tcx, myitem, item, None), - class = myitem.type_(), - unsafety_flag = unsafety_flag, - href = print_item_path(myitem.type_(), myitem.name.unwrap().as_str()), - title1 = myitem.type_(), - title2 = full_path(cx, myitem), - )?; + let unsafety_flag = match myitem.kind { + clean::FunctionItem(_) | clean::ForeignFunctionItem(..) + if myitem.fn_header(tcx).unwrap().safety + == hir::HeaderSafety::Normal(hir::Safety::Unsafe) => + { + "" + } + clean::ForeignStaticItem(_, hir::Safety::Unsafe) => { + "" + } + _ => "", + }; + let visibility_and_hidden = match myitem.visibility(tcx) { + Some(ty::Visibility::Restricted(_)) => { + if myitem.is_doc_hidden() { + // Don't separate with a space when there are two of them + " 🔒👻 " + } else { + " 🔒 " + } + } + _ if myitem.is_doc_hidden() => { + " 👻 " + } + _ => "", + }; + + let docs = MarkdownSummaryLine(&myitem.doc_value(), &myitem.links(cx)) + .into_string(); + let (docs_before, docs_after) = + if docs.is_empty() { ("", "") } else { ("
", "
") }; + write!( + w, + "
\ + \ + {name}\ + \ + {visibility_and_hidden}\ + {unsafety_flag}\ + {stab_tags}\ +
\ + {docs_before}{docs}{docs_after}", + name = EscapeBodyTextWithWbr(myitem.name.unwrap().as_str()), + visibility_and_hidden = visibility_and_hidden, + stab_tags = print_extra_info_tags(tcx, myitem, item, None), + class = type_, + unsafety_flag = unsafety_flag, + href = print_item_path(type_, myitem.name.unwrap().as_str()), + title1 = myitem.type_(), + title2 = full_path(cx, myitem), + )?; + } } } - } - - if last_section.is_some() { w.write_str(ITEM_TABLE_CLOSE)?; } + Ok(()) }) } From ebd5bea84fee777c5f79bb18e39f28b3f666206a Mon Sep 17 00:00:00 2001 From: ltdk Date: Thu, 23 Oct 2025 03:22:56 -0400 Subject: [PATCH 2/7] Revert inference failure from Deref/Borrow constification --- library/alloc/src/borrow.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/library/alloc/src/borrow.rs b/library/alloc/src/borrow.rs index 4659fb2a8426d..d2ab5412eeabc 100644 --- a/library/alloc/src/borrow.rs +++ b/library/alloc/src/borrow.rs @@ -16,12 +16,13 @@ use crate::fmt; #[cfg(not(no_global_oom_handling))] use crate::string::String; +// FIXME(inference): const bounds removed due to inference regressions found by crater; +// see https://github.com/rust-lang/rust/issues/147964 +// #[rustc_const_unstable(feature = "const_convert", issue = "143773")] #[stable(feature = "rust1", since = "1.0.0")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -impl<'a, B: ?Sized> const Borrow for Cow<'a, B> -where - B: ToOwned, - B::Owned: [const] Borrow, +impl<'a, B: ?Sized + ToOwned> Borrow for Cow<'a, B> +// where +// B::Owned: [const] Borrow, { fn borrow(&self) -> &B { &**self @@ -327,11 +328,13 @@ impl Cow<'_, B> { } } +// FIXME(inference): const bounds removed due to inference regressions found by crater; +// see https://github.com/rust-lang/rust/issues/147964 +// #[rustc_const_unstable(feature = "const_convert", issue = "143773")] #[stable(feature = "rust1", since = "1.0.0")] -#[rustc_const_unstable(feature = "const_convert", issue = "143773")] -impl const Deref for Cow<'_, B> -where - B::Owned: [const] Borrow, +impl Deref for Cow<'_, B> +// where +// B::Owned: [const] Borrow, { type Target = B; From 9ceb997e040250e0bdb425b324b7662abd1d42a2 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 23 Oct 2025 17:52:54 +0200 Subject: [PATCH 3/7] Add myself to the review rotation --- triagebot.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/triagebot.toml b/triagebot.toml index fbe29dd182452..a0d0b1892e4fe 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1370,6 +1370,7 @@ compiler = [ "@jackh726", "@jieyouxu", "@jdonszelmann", + "@JonathanBrouwer", "@lcnr", "@madsmtm", "@Nadrieril", From bca35effc216ce45bd07c25c706a261806f71b98 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Thu, 23 Oct 2025 11:47:53 -0500 Subject: [PATCH 4/7] test(frontmatter): Cover spaces between infostring parts As these characters are specifically called out in the RFC, I felt it would be important to have a test to cover them. --- tests/ui/frontmatter/space-in-infostring.rs | 9 +++++++++ tests/ui/frontmatter/space-in-infostring.stderr | 10 ++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/ui/frontmatter/space-in-infostring.rs create mode 100644 tests/ui/frontmatter/space-in-infostring.stderr diff --git a/tests/ui/frontmatter/space-in-infostring.rs b/tests/ui/frontmatter/space-in-infostring.rs new file mode 100644 index 0000000000000..a8448af113443 --- /dev/null +++ b/tests/ui/frontmatter/space-in-infostring.rs @@ -0,0 +1,9 @@ +--- cargo clippy +//~^ ERROR: invalid infostring for frontmatter +--- + +// infostrings cannot have spaces + +#![feature(frontmatter)] + +fn main() {} diff --git a/tests/ui/frontmatter/space-in-infostring.stderr b/tests/ui/frontmatter/space-in-infostring.stderr new file mode 100644 index 0000000000000..b876ddae782e9 --- /dev/null +++ b/tests/ui/frontmatter/space-in-infostring.stderr @@ -0,0 +1,10 @@ +error: invalid infostring for frontmatter + --> $DIR/space-in-infostring.rs:1:4 + | +LL | --- cargo clippy + | ^^^^^^^^^^^^^ + | + = note: frontmatter infostrings must be a single identifier immediately following the opening + +error: aborting due to 1 previous error + From 90c047b3d9bc03443bc516280225903303c27f18 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 23 Oct 2025 21:45:37 +0200 Subject: [PATCH 5/7] Replace the `FxHashMap` with an `FxIndexMap` --- src/librustdoc/html/render/print_item.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index b9aac4c7c51c1..6bb8f2043f4bb 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -5,7 +5,7 @@ use std::iter; use askama::Template; use rustc_abi::VariantIdx; use rustc_ast::join_path_syms; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_hir as hir; use rustc_hir::def::CtorKind; use rustc_hir::def_id::DefId; @@ -307,8 +307,8 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i fmt::from_fn(|w| { write!(w, "{}", document(cx, item, None, HeadingOffset::H2))?; - let mut not_stripped_items: FxHashMap> = - FxHashMap::default(); + let mut not_stripped_items: FxIndexMap> = + FxIndexMap::default(); for (index, item) in items.iter().filter(|i| !i.is_stripped()).enumerate() { not_stripped_items.entry(item.type_()).or_default().push((index, item)); @@ -355,9 +355,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i let tcx = cx.tcx(); match cx.shared.module_sorting { - ModuleSorting::Alphabetical => - { - #[allow(rustc::potential_query_instability)] + ModuleSorting::Alphabetical => { for items in not_stripped_items.values_mut() { items.sort_by(|(_, i1), (_, i2)| cmp(i1, i2, tcx)); } @@ -383,7 +381,6 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i // can be identical even if the elements are different (mostly in imports). // So in case this is an import, we keep everything by adding a "unique id" // (which is the position in the vector). - #[allow(rustc::potential_query_instability)] for items in not_stripped_items.values_mut() { items.dedup_by_key(|(idx, i)| { ( @@ -397,7 +394,6 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i debug!("{not_stripped_items:?}"); - #[allow(rustc::potential_query_instability)] let mut types = not_stripped_items.keys().copied().collect::>(); types.sort_unstable_by(|a, b| reorder(*a).cmp(&reorder(*b))); From 3d951598b295bb2ee1396ef0622e09400c45e273 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 23 Oct 2025 10:21:39 +1100 Subject: [PATCH 6/7] Streamline iterator chaining when computing successors. There are numerous unnecessary `into_iter` calls. Also add a comment explaining why the code looks like this, because it's non-obvious at first glance. --- compiler/rustc_middle/src/mir/terminator.rs | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index 62711ad92372a..db8251c7d9dc8 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -501,6 +501,8 @@ pub use helper::*; mod helper { use super::*; + // Note: the methods below use a `slice.chain(Option).chain(Option)` pattern so that all paths + // produce an iterator with the same concrete type. pub type Successors<'a> = impl DoubleEndedIterator + 'a; impl SwitchTargets { @@ -510,7 +512,7 @@ mod helper { #[define_opaque(Successors)] pub fn successors_for_value(&self, value: u128) -> Successors<'_> { let target = self.target_for_value(value); - (&[]).into_iter().copied().chain(Some(target).into_iter().chain(None)) + (&[]).into_iter().copied().chain(Some(target)).chain(None) } } @@ -522,10 +524,7 @@ mod helper { match *self { // 3-successors for async drop: target, unwind, dropline (parent coroutine drop) Drop { target: ref t, unwind: UnwindAction::Cleanup(u), drop: Some(d), .. } => { - slice::from_ref(t) - .into_iter() - .copied() - .chain(Some(u).into_iter().chain(Some(d))) + slice::from_ref(t).into_iter().copied().chain(Some(u)).chain(Some(d)) } // 2-successors Call { target: Some(ref t), unwind: UnwindAction::Cleanup(u), .. } @@ -534,7 +533,7 @@ mod helper { | Drop { target: ref t, unwind: _, drop: Some(u), .. } | Assert { target: ref t, unwind: UnwindAction::Cleanup(u), .. } | FalseUnwind { real_target: ref t, unwind: UnwindAction::Cleanup(u) } => { - slice::from_ref(t).into_iter().copied().chain(Some(u).into_iter().chain(None)) + slice::from_ref(t).into_iter().copied().chain(Some(u)).chain(None) } // single successor Goto { target: ref t } @@ -544,7 +543,7 @@ mod helper { | Drop { target: ref t, unwind: _, .. } | Assert { target: ref t, unwind: _, .. } | FalseUnwind { real_target: ref t, unwind: _ } => { - slice::from_ref(t).into_iter().copied().chain(None.into_iter().chain(None)) + slice::from_ref(t).into_iter().copied().chain(None).chain(None) } // No successors UnwindResume @@ -554,23 +553,24 @@ mod helper { | Unreachable | TailCall { .. } | Call { target: None, unwind: _, .. } => { - (&[]).into_iter().copied().chain(None.into_iter().chain(None)) + (&[]).into_iter().copied().chain(None).chain(None) } // Multiple successors InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => { - targets.iter().copied().chain(Some(u).into_iter().chain(None)) + targets.iter().copied().chain(Some(u)).chain(None) } InlineAsm { ref targets, unwind: _, .. } => { - targets.iter().copied().chain(None.into_iter().chain(None)) + targets.iter().copied().chain(None).chain(None) } SwitchInt { ref targets, .. } => { - targets.targets.iter().copied().chain(None.into_iter().chain(None)) + targets.targets.iter().copied().chain(None).chain(None) } // FalseEdge FalseEdge { ref real_target, imaginary_target } => slice::from_ref(real_target) .into_iter() .copied() - .chain(Some(imaginary_target).into_iter().chain(None)), + .chain(Some(imaginary_target)) + .chain(None), } } From 4a4f3b0e8e9609136c10a190c3f5572d70bb33ee Mon Sep 17 00:00:00 2001 From: ltdk Date: Thu, 23 Oct 2025 18:24:39 -0400 Subject: [PATCH 7/7] Add regression test for inference failures --- .../generic-cow-inference-regression.rs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/ui/traits/generic-cow-inference-regression.rs diff --git a/tests/ui/traits/generic-cow-inference-regression.rs b/tests/ui/traits/generic-cow-inference-regression.rs new file mode 100644 index 0000000000000..6fd4715f85bbd --- /dev/null +++ b/tests/ui/traits/generic-cow-inference-regression.rs @@ -0,0 +1,20 @@ +//@ run-pass + +// regression test for #147964: +// constification of these traits resulted in inference errors due to additional where clauses + +use std::borrow::{Cow, Borrow}; + +pub fn generic_deref<'a, T: ToOwned, U>(cow: Cow<'a, T>) { + let _: &T = &cow; +} + +pub fn generic_borrow<'a, T: ToOwned, U>(cow: Cow<'a, T>) { + let _: &T = cow.borrow(); +} + +pub fn generic_as_ref<'a, T: ToOwned, U>(cow: Cow<'a, T>) { + let _: &T = cow.as_ref(); +} + +fn main() {}