Skip to content

Commit 52cadc0

Browse files
authored
Rollup merge of #142380 - GuillaumeGomez:neg-implementors, r=yotamofek
Put negative implementors first and apply same ordering logic to foreign implementors Fixes #51129. This PR changeda surprisingly small amount of things to put negative trait impls before the others: basically just adding a new information in the generated JS file for foreign implementors and a "negative marker" DOM element to know where to insert negative impls. I also used this occasion to make the foreign implementors sort the same as the local ones by using `compare_names`. You can test it [here](https://rustdoc.crud.net/imperio/neg-implementors/core/marker/trait.Sync.html#implementors). r? ```@notriddle```
2 parents f520900 + fb9d3a5 commit 52cadc0

File tree

11 files changed

+173
-32
lines changed

11 files changed

+173
-32
lines changed

src/librustdoc/formats/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,8 @@ impl Impl {
7676
};
7777
true
7878
}
79+
80+
pub(crate) fn is_negative_trait_impl(&self) -> bool {
81+
self.inner_impl().is_negative_trait_impl()
82+
}
7983
}

src/librustdoc/html/render/print_item.rs

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,27 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp
646646
})
647647
}
648648

649+
/// Struct used to handle insertion of "negative impl" marker in the generated DOM.
650+
///
651+
/// This marker appears once in all trait impl lists to divide negative impls from positive impls.
652+
struct NegativeMarker {
653+
inserted: bool,
654+
}
655+
656+
impl NegativeMarker {
657+
fn new() -> Self {
658+
Self { inserted: false }
659+
}
660+
661+
fn insert_if_needed(&mut self, w: &mut fmt::Formatter<'_>, implementor: &Impl) -> fmt::Result {
662+
if !self.inserted && !implementor.is_negative_trait_impl() {
663+
w.write_str("<div class=\"negative-marker\"></div>")?;
664+
self.inserted = true;
665+
}
666+
Ok(())
667+
}
668+
}
669+
649670
fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt::Display {
650671
fmt::from_fn(|w| {
651672
let tcx = cx.tcx();
@@ -1072,7 +1093,9 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
10721093
"<div id=\"implementors-list\">",
10731094
)
10741095
)?;
1096+
let mut negative_marker = NegativeMarker::new();
10751097
for implementor in concrete {
1098+
negative_marker.insert_if_needed(w, implementor)?;
10761099
write!(w, "{}", render_implementor(cx, implementor, it, &implementor_dups, &[]))?;
10771100
}
10781101
w.write_str("</div>")?;
@@ -1088,7 +1111,9 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt:
10881111
"<div id=\"synthetic-implementors-list\">",
10891112
)
10901113
)?;
1114+
let mut negative_marker = NegativeMarker::new();
10911115
for implementor in synthetic {
1116+
negative_marker.insert_if_needed(w, implementor)?;
10921117
write!(
10931118
w,
10941119
"{}",
@@ -2302,11 +2327,18 @@ where
23022327
}
23032328

23042329
#[derive(PartialEq, Eq)]
2305-
struct ImplString(String);
2330+
struct ImplString {
2331+
rendered: String,
2332+
is_negative: bool,
2333+
}
23062334

23072335
impl ImplString {
23082336
fn new(i: &Impl, cx: &Context<'_>) -> ImplString {
2309-
ImplString(format!("{}", print_impl(i.inner_impl(), false, cx)))
2337+
let impl_ = i.inner_impl();
2338+
ImplString {
2339+
is_negative: impl_.is_negative_trait_impl(),
2340+
rendered: format!("{}", print_impl(impl_, false, cx)),
2341+
}
23102342
}
23112343
}
23122344

@@ -2318,7 +2350,12 @@ impl PartialOrd for ImplString {
23182350

23192351
impl Ord for ImplString {
23202352
fn cmp(&self, other: &Self) -> Ordering {
2321-
compare_names(&self.0, &other.0)
2353+
// We sort negative impls first.
2354+
match (self.is_negative, other.is_negative) {
2355+
(false, true) => Ordering::Greater,
2356+
(true, false) => Ordering::Less,
2357+
_ => compare_names(&self.rendered, &other.rendered),
2358+
}
23222359
}
23232360
}
23242361

src/librustdoc/html/render/write_shared.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
//! or contains "invocation-specific".
1515
1616
use std::cell::RefCell;
17+
use std::cmp::Ordering;
1718
use std::ffi::{OsStr, OsString};
1819
use std::fs::File;
1920
use std::io::{self, Write as _};
@@ -47,6 +48,7 @@ use crate::formats::item_type::ItemType;
4748
use crate::html::format::{print_impl, print_path};
4849
use crate::html::layout;
4950
use crate::html::render::ordered_json::{EscapedJson, OrderedJson};
51+
use crate::html::render::print_item::compare_names;
5052
use crate::html::render::search_index::{SerializedSearchIndex, build_index};
5153
use crate::html::render::sorted_template::{self, FileFormat, SortedTemplate};
5254
use crate::html::render::{AssocItemLink, ImplRenderingParameters, StylePath};
@@ -667,7 +669,7 @@ impl TraitAliasPart {
667669
fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
668670
SortedTemplate::from_before_after(
669671
r"(function() {
670-
var implementors = Object.fromEntries([",
672+
const implementors = Object.fromEntries([",
671673
r"]);
672674
if (window.register_implementors) {
673675
window.register_implementors(implementors);
@@ -720,10 +722,12 @@ impl TraitAliasPart {
720722
{
721723
None
722724
} else {
725+
let impl_ = imp.inner_impl();
723726
Some(Implementor {
724-
text: print_impl(imp.inner_impl(), false, cx).to_string(),
727+
text: print_impl(impl_, false, cx).to_string(),
725728
synthetic: imp.inner_impl().kind.is_auto(),
726729
types: collect_paths_for_type(&imp.inner_impl().for_, cache),
730+
is_negative: impl_.is_negative_trait_impl(),
727731
})
728732
}
729733
})
@@ -742,8 +746,22 @@ impl TraitAliasPart {
742746
}
743747
path.push(format!("{remote_item_type}.{}.js", remote_path[remote_path.len() - 1]));
744748

745-
let part = OrderedJson::array_sorted(
746-
implementors.map(|implementor| OrderedJson::serialize(implementor).unwrap()),
749+
let mut implementors = implementors.collect::<Vec<_>>();
750+
implementors.sort_unstable_by(|a, b| {
751+
// We sort negative impls first.
752+
match (a.is_negative, b.is_negative) {
753+
(false, true) => Ordering::Greater,
754+
(true, false) => Ordering::Less,
755+
_ => compare_names(&a.text, &b.text),
756+
}
757+
});
758+
759+
let part = OrderedJson::array_unsorted(
760+
implementors
761+
.iter()
762+
.map(OrderedJson::serialize)
763+
.collect::<Result<Vec<_>, _>>()
764+
.unwrap(),
747765
);
748766
path_parts.push(path, OrderedJson::array_unsorted([crate_name_json, &part]));
749767
}
@@ -755,6 +773,7 @@ struct Implementor {
755773
text: String,
756774
synthetic: bool,
757775
types: Vec<String>,
776+
is_negative: bool,
758777
}
759778

760779
impl Serialize for Implementor {
@@ -764,6 +783,7 @@ impl Serialize for Implementor {
764783
{
765784
let mut seq = serializer.serialize_seq(None)?;
766785
seq.serialize_element(&self.text)?;
786+
seq.serialize_element(if self.is_negative { &1 } else { &0 })?;
767787
if self.synthetic {
768788
seq.serialize_element(&1)?;
769789
seq.serialize_element(&self.types)?;

src/librustdoc/html/render/write_shared/tests.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn trait_alias_template() {
6868
assert_eq!(
6969
but_last_line(&template.to_string()),
7070
r#"(function() {
71-
var implementors = Object.fromEntries([]);
71+
const implementors = Object.fromEntries([]);
7272
if (window.register_implementors) {
7373
window.register_implementors(implementors);
7474
} else {
@@ -80,7 +80,7 @@ fn trait_alias_template() {
8080
assert_eq!(
8181
but_last_line(&template.to_string()),
8282
r#"(function() {
83-
var implementors = Object.fromEntries([["a"]]);
83+
const implementors = Object.fromEntries([["a"]]);
8484
if (window.register_implementors) {
8585
window.register_implementors(implementors);
8686
} else {
@@ -92,7 +92,7 @@ fn trait_alias_template() {
9292
assert_eq!(
9393
but_last_line(&template.to_string()),
9494
r#"(function() {
95-
var implementors = Object.fromEntries([["a"],["b"]]);
95+
const implementors = Object.fromEntries([["a"],["b"]]);
9696
if (window.register_implementors) {
9797
window.register_implementors(implementors);
9898
} else {

src/librustdoc/html/static/css/noscript.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ nav.sub {
2929
display: none;
3030
}
3131

32+
#synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) {
33+
display: block;
34+
}
35+
3236
/* Begin: styles for themes
3337
Keep the default light and dark themes synchronized with the ones
3438
in rustdoc.css */

src/librustdoc/html/static/css/rustdoc.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,12 @@ div.where {
11581158
margin-left: calc(var(--docblock-indent) + var(--impl-items-indent));
11591159
}
11601160

1161+
#synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) {
1162+
/* To prevent layout shift when loading the page with extra implementors being loaded
1163+
from JS, we hide the list until it's complete. */
1164+
display: none;
1165+
}
1166+
11611167
.item-info code {
11621168
font-size: 0.875rem;
11631169
}
@@ -2976,6 +2982,9 @@ in src-script.js and main.js
29762982
{
29772983
margin-bottom: 0.75em;
29782984
}
2985+
.negative-marker {
2986+
display: none;
2987+
}
29792988

29802989
.variants > .docblock,
29812990
.implementors-toggle > .docblock,

src/librustdoc/html/static/js/main.js

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -800,21 +800,34 @@ function preLoadCss(cssUrl) {
800800

801801
// <https://github.com/search?q=repo%3Arust-lang%2Frust+[RUSTDOCIMPL]+trait.impl&type=code>
802802
window.register_implementors = imp => {
803-
const implementors = document.getElementById("implementors-list");
804-
const synthetic_implementors = document.getElementById("synthetic-implementors-list");
803+
/** Takes an ID as input and returns a list of two elements. The first element is the DOM
804+
* element with the given ID and the second is the "negative marker", meaning the location
805+
* between the negative and non-negative impls.
806+
*
807+
* @param {string} id: ID of the DOM element.
808+
*
809+
* @return {[HTMLElement|null, HTMLElement|null]}
810+
*/
811+
function implementorsElems(id) {
812+
const elem = document.getElementById(id);
813+
return [elem, elem ? elem.querySelector(".negative-marker") : null];
814+
}
815+
const implementors = implementorsElems("implementors-list");
816+
const syntheticImplementors = implementorsElems("synthetic-implementors-list");
805817
const inlined_types = new Set();
806818

807819
const TEXT_IDX = 0;
808-
const SYNTHETIC_IDX = 1;
809-
const TYPES_IDX = 2;
820+
const IS_NEG_IDX = 1;
821+
const SYNTHETIC_IDX = 2;
822+
const TYPES_IDX = 3;
810823

811-
if (synthetic_implementors) {
824+
if (syntheticImplementors[0]) {
812825
// This `inlined_types` variable is used to avoid having the same implementation
813826
// showing up twice. For example "String" in the "Sync" doc page.
814827
//
815828
// By the way, this is only used by and useful for traits implemented automatically
816829
// (like "Send" and "Sync").
817-
onEachLazy(synthetic_implementors.getElementsByClassName("impl"), el => {
830+
onEachLazy(syntheticImplementors[0].getElementsByClassName("impl"), el => {
818831
const aliases = el.getAttribute("data-aliases");
819832
if (!aliases) {
820833
return;
@@ -827,7 +840,7 @@ function preLoadCss(cssUrl) {
827840
}
828841

829842
// @ts-expect-error
830-
let currentNbImpls = implementors.getElementsByClassName("impl").length;
843+
let currentNbImpls = implementors[0].getElementsByClassName("impl").length;
831844
// @ts-expect-error
832845
const traitName = document.querySelector(".main-heading h1 > .trait").textContent;
833846
const baseIdName = "impl-" + traitName + "-";
@@ -849,7 +862,7 @@ function preLoadCss(cssUrl) {
849862

850863
struct_loop:
851864
for (const struct of structs) {
852-
const list = struct[SYNTHETIC_IDX] ? synthetic_implementors : implementors;
865+
const list = struct[SYNTHETIC_IDX] ? syntheticImplementors : implementors;
853866

854867
// The types list is only used for synthetic impls.
855868
// If this changes, `main.js` and `write_shared.rs` both need changed.
@@ -884,11 +897,25 @@ function preLoadCss(cssUrl) {
884897
addClass(display, "impl");
885898
display.appendChild(anchor);
886899
display.appendChild(code);
887-
// @ts-expect-error
888-
list.appendChild(display);
900+
901+
// If this is a negative implementor, we put it into the right location (just
902+
// before the negative impl marker).
903+
if (struct[IS_NEG_IDX]) {
904+
// @ts-expect-error
905+
list[1].before(display);
906+
} else {
907+
// @ts-expect-error
908+
list[0].appendChild(display);
909+
}
889910
currentNbImpls += 1;
890911
}
891912
}
913+
if (implementors[0]) {
914+
implementors[0].classList.add("loaded");
915+
}
916+
if (syntheticImplementors[0]) {
917+
syntheticImplementors[0].classList.add("loaded");
918+
}
892919
};
893920
if (window.pending_implementors) {
894921
window.register_implementors(window.pending_implementors);

src/librustdoc/html/static/js/rustdoc.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ declare namespace rustdoc {
520520
* Provided by generated `trait.impl` files.
521521
*/
522522
type Implementors = {
523-
[key: string]: Array<[string, number, Array<string>]>
523+
[key: string]: Array<[string, 0|1, number, Array<string>]>
524524
}
525525

526526
type TypeImpls = {

tests/rustdoc-gui/implementors.goml

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,46 @@
11
// The goal of this test is to check that the external trait implementors, generated with JS,
22
// have the same display than the "local" ones.
33
go-to: "file://" + |DOC_PATH| + "/implementors/trait.Whatever.html"
4-
assert: "#implementors-list"
5-
// There are supposed to be two implementors listed.
6-
assert-count: ("#implementors-list .impl", 2)
4+
wait-for-css: ("#implementors-list", {"display": "block"})
5+
// There are supposed to be four implementors listed.
6+
assert-count: ("#implementors-list .impl", 4)
7+
// There are supposed to be two non-negative implementors.
8+
assert-count: ("#implementors-list .negative-marker ~ *", 2)
79
// Now we check that both implementors have an anchor, an ID and a similar DOM.
8-
assert: ("#implementors-list .impl:nth-child(1) > a.anchor")
9-
assert-attribute: ("#implementors-list .impl:nth-child(1)", {"id": "impl-Whatever-for-Struct"})
10-
assert-attribute: ("#implementors-list .impl:nth-child(1) > a.anchor", {"href": "#impl-Whatever-for-Struct"})
11-
assert: "#implementors-list .impl:nth-child(1) > .code-header"
10+
define-function: (
11+
"check-dom",
12+
[id],
13+
block {
14+
assert-attribute: (|id| + " > a.anchor", {"href": |id|})
15+
assert: |id| + " > .code-header"
16+
},
17+
)
1218

13-
assert: ("#implementors-list .impl:nth-child(2) > a.anchor")
14-
assert-attribute: ("#implementors-list .impl:nth-child(2)", {"id": "impl-Whatever-1"})
15-
assert-attribute: ("#implementors-list .impl:nth-child(2) > a.anchor", {"href": "#impl-Whatever-1"})
16-
assert: "#implementors-list .impl:nth-child(2) > .code-header"
19+
call-function: ("check-dom", {"id": "#impl-Whatever-for-Struct2"})
20+
call-function: ("check-dom", {"id": "#impl-Whatever-2"})
21+
call-function: ("check-dom", {"id": "#impl-Whatever-for-Struct"})
22+
call-function: ("check-dom", {"id": "#impl-Whatever-3"})
23+
24+
// Ensure that negative impl are sorted first.
25+
assert-property: (
26+
"#implementors-list > *:nth-child(1) > h3",
27+
{"textContent": "impl !Whatever for Struct2"},
28+
)
29+
assert-property: (
30+
"#implementors-list > *:nth-child(2) > h3",
31+
{"textContent": "impl !Whatever for StructToImplOnReexport"},
32+
)
33+
// Third one is the negative marker.
34+
assert-attribute: ("#implementors-list > *:nth-child(3)", {"class": "negative-marker"})
35+
// This one is a `<detail>` so the selector is a bit different.
36+
assert-property: (
37+
"#implementors-list > *:nth-child(4) section > h3",
38+
{"textContent": "impl Whatever for Struct"},
39+
)
40+
assert-property: (
41+
"#implementors-list > *:nth-child(5) > h3",
42+
{"textContent": "impl Whatever for Foo"},
43+
)
1744

1845
go-to: "file://" + |DOC_PATH| + "/test_docs/struct.HasEmptyTraits.html"
1946
compare-elements-position-near-false: (
@@ -39,3 +66,8 @@ assert-count: ("#implementors-list .impl", 1)
3966
go-to: "file://" + |DOC_PATH| + "/http/trait.HttpTrait.html"
4067
assert-count: ("#implementors-list .impl", 1)
4168
assert-attribute: ("#implementors-list .impl a.trait", {"href": "../http/trait.HttpTrait.html"})
69+
70+
// Now we check that if JS is disabled, the implementors list will be visible.
71+
javascript: false
72+
reload:
73+
assert-css: ("#implementors-list", {"display": "block"})

0 commit comments

Comments
 (0)