diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 8108316a856ba..00a20fdca6e31 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -79,6 +79,7 @@ use crate::html::format::{
use crate::html::markdown::{
HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine, short_markdown_summary,
};
+use crate::html::render::print_item::compare_names;
use crate::html::render::search_index::get_function_type_for_search;
use crate::html::static_files::SCRAPE_EXAMPLES_HELP_MD;
use crate::html::{highlight, sources};
@@ -941,6 +942,16 @@ fn short_item_info(
extra_info
}
+// Prints the polarity and path of an impl's trait, if it has one, e.g. `Send`, `!Sync`.
+fn impl_trait_key(cx: &Context<'_>, i: &Impl) -> Option {
+ let trait_ = i.inner_impl().trait_.as_ref()?;
+ let prefix = match i.inner_impl().polarity {
+ ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
+ ty::ImplPolarity::Negative => "!",
+ };
+ Some(format!("{prefix}{:#}", print_path(trait_, cx)))
+}
+
// Render the list of items inside one of the sections "Trait Implementations",
// "Auto Trait Implementations," "Blanket Trait Implementations" (on struct/enum pages).
fn render_impls(
@@ -950,7 +961,9 @@ fn render_impls(
containing_item: &clean::Item,
toggle_open_by_default: bool,
) -> fmt::Result {
- let mut rendered_impls = impls
+ // Render each impl alongside its `impl_trait_key`, which is used as the primary sorting key
+ // to match the impl order in the sidebar.
+ let mut keyed_rendered_impls = impls
.iter()
.map(|i| {
let did = i.trait_did().unwrap();
@@ -971,11 +984,16 @@ fn render_impls(
toggle_open_by_default,
},
);
- imp.to_string()
+ (impl_trait_key(cx, i).unwrap(), imp.to_string())
})
.collect::>();
- rendered_impls.sort();
- w.write_str(&rendered_impls.join(""))
+
+ // Sort and then remove the `impl_trait_key`s, which are no longer needed after sorting.
+ keyed_rendered_impls
+ .sort_by(|(k1, h1), (k2, h2)| compare_names(k1, k2).then_with(|| h1.cmp(h2)));
+ let joined: String = keyed_rendered_impls.into_iter().map(|a| a.1).collect();
+
+ w.write_str(&joined)
}
/// Build a (possibly empty) `href` attribute (a key-value pair) for the given associated item.
diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs
index 44c8013664cae..beddac0f2613f 100644
--- a/src/librustdoc/html/render/sidebar.rs
+++ b/src/librustdoc/html/render/sidebar.rs
@@ -6,10 +6,10 @@ use askama::Template;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def::CtorKind;
use rustc_hir::def_id::{DefIdMap, DefIdSet};
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_middle::ty::TyCtxt;
use tracing::debug;
-use super::{Context, ItemSection, item_ty_to_section};
+use super::{Context, ItemSection, impl_trait_key, item_ty_to_section};
use crate::clean;
use crate::formats::Impl;
use crate::formats::item_type::ItemType;
@@ -707,15 +707,9 @@ fn sidebar_render_assoc_items(
let mut ret = impls
.iter()
- .filter_map(|it| {
- let trait_ = it.inner_impl().trait_.as_ref()?;
- let encoded = id_map.derive(super::get_id_for_impl(cx.tcx(), it.impl_item.item_id));
-
- let prefix = match it.inner_impl().polarity {
- ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "",
- ty::ImplPolarity::Negative => "!",
- };
- let generated = Link::new(encoded, format!("{prefix}{:#}", print_path(trait_, cx)));
+ .filter_map(|i| {
+ let encoded = id_map.derive(super::get_id_for_impl(cx.tcx(), i.impl_item.item_id));
+ let generated = Link::new(encoded, impl_trait_key(cx, i)?);
if links.insert(generated.clone()) { Some(generated) } else { None }
})
.collect::>>();
diff --git a/tests/rustdoc-gui/search-tab.goml b/tests/rustdoc-gui/search-tab.goml
index 0a3cfc231e50a..34daf498b5b69 100644
--- a/tests/rustdoc-gui/search-tab.goml
+++ b/tests/rustdoc-gui/search-tab.goml
@@ -81,7 +81,7 @@ set-window-size: (851, 600)
// Check the size and count in tabs
assert-text: ("#search-tabs > button:nth-child(1) > .count", " (25) ")
assert-text: ("#search-tabs > button:nth-child(2) > .count", " (7) ")
-assert-text: ("#search-tabs > button:nth-child(3) > .count", " (0) ")
+assert-text: ("#search-tabs > button:nth-child(3) > .count", " (1) ")
store-property: ("#search-tabs > button:nth-child(1)", {"offsetWidth": buttonWidth})
assert-property: ("#search-tabs > button:nth-child(2)", {"offsetWidth": |buttonWidth|})
assert-property: ("#search-tabs > button:nth-child(3)", {"offsetWidth": |buttonWidth|})
diff --git a/tests/rustdoc-gui/sidebar-foreign-impl-sort.goml b/tests/rustdoc-gui/sidebar-foreign-impl-sort.goml
index f09f09713514d..31b35f5152327 100644
--- a/tests/rustdoc-gui/sidebar-foreign-impl-sort.goml
+++ b/tests/rustdoc-gui/sidebar-foreign-impl-sort.goml
@@ -1,4 +1,4 @@
-// Checks sidebar resizing close the Settings popover
+// Checks sidebar foreign impl ordering
go-to: "file://" + |DOC_PATH| + "/test_docs/SidebarSort/trait.Sort.html#foreign-impls"
// Check that the sidebar contains the expected foreign implementations
diff --git a/tests/rustdoc-gui/src/test_docs/lib.rs b/tests/rustdoc-gui/src/test_docs/lib.rs
index f219291970450..49ac8bd31a49e 100644
--- a/tests/rustdoc-gui/src/test_docs/lib.rs
+++ b/tests/rustdoc-gui/src/test_docs/lib.rs
@@ -10,6 +10,7 @@
#![feature(associated_type_defaults)]
#![feature(macro_attr)]
#![feature(macro_derive)]
+#![feature(negative_impls)]
/*!
Enable the feature some-feature to enjoy
@@ -89,6 +90,19 @@ impl AsRef for Foo {
}
}
+unsafe impl Send for Foo {}
+impl !Sync for Foo {}
+
+impl From for Foo {
+ fn from(value: u8) -> Self { todo!(); }
+}
+impl From for Foo {
+ fn from(value: u16) -> Self { todo!(); }
+}
+impl From for Foo {
+ fn from(value: u32) -> Self { todo!(); }
+}
+
/// I have warnings!
pub struct WarningStruct;
diff --git a/tests/rustdoc-gui/trait-impl-sort.goml b/tests/rustdoc-gui/trait-impl-sort.goml
new file mode 100644
index 0000000000000..2d67bdf5bd58f
--- /dev/null
+++ b/tests/rustdoc-gui/trait-impl-sort.goml
@@ -0,0 +1,19 @@
+// Check that trait impls in the sidebar and the main section have the same
+// ordering.
+
+go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
+
+// .sidebar .trait-implementation li:nth-child(3) a
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(1) a", "!Sync")
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(2) a", "AsRef")
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(3) a", "From")
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(4) a", "From")
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(5) a", "From")
+assert-text: (".sidebar-elems .trait-implementation li:nth-child(6) a", "Send")
+
+assert-text: ("#trait-implementations-list section:nth-child(1) .code-header", "impl !Sync for Foo")
+assert-text: ("#trait-implementations-list details:nth-child(2) .code-header", "impl AsRef for Foo")
+assert-text: ("#trait-implementations-list details:nth-child(3) .code-header", "impl From for Foo")
+assert-text: ("#trait-implementations-list details:nth-child(4) .code-header", "impl From for Foo")
+assert-text: ("#trait-implementations-list details:nth-child(5) .code-header", "impl From for Foo")
+assert-text: ("#trait-implementations-list section:nth-child(6) .code-header", "impl Send for Foo")