Skip to content

Conversation

@GuillaumeGomez
Copy link
Member

@GuillaumeGomez GuillaumeGomez commented Jun 11, 2025

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.

r? @notriddle

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output. labels Jun 11, 2025
@rustbot
Copy link
Collaborator

rustbot commented Jun 11, 2025

Some changes occurred in HTML/CSS/JS.

cc @GuillaumeGomez, @jsha, @lolbinarycat

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@GuillaumeGomez
Copy link
Member Author

Took me a while to figure out where the typescript definition was stored to fix the error. ^^'

let part = OrderedJson::array_sorted(
implementors.sort_unstable();

let part = OrderedJson::array_unsorted(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely an improvement, as it eliminates an allocation, but we're still allocating an intermediate string for each element.

OrderedJson could be much more efficient if it had a method that appended using serde_json::to_writer.

I can open an issue for improving the OrderedJson interface, if you want.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An issue would be welcome indeed.

@rust-log-analyzer

This comment has been minimized.

rust-bors bot added a commit that referenced this pull request Jun 17, 2025
alternate interface for OrderedJson to reduce allocations

inspired by #142380 (comment)

r? `@GuillaumeGomez`
<!-- homu-ignore:start -->
<!--
If this PR is related to an unstable feature or an otherwise tracked effort,
please link to the relevant tracking issue here. If you don't know of a related
tracking issue or there are none, feel free to ignore this.

This PR will get automatically assigned to a reviewer. In case you would like
a specific user to review your work, you can assign it to them by using

    r? <reviewer name>
-->
<!-- homu-ignore:end -->
@bors
Copy link
Collaborator

bors commented Jun 22, 2025

☔ The latest upstream changes (presumably #142667) made this pull request unmergeable. Please resolve the merge conflicts.

@GuillaumeGomez
Copy link
Member Author

Fixed merge conflicts.

@notriddle
Copy link
Contributor

Either JavaScript-loaded content needs to go on the bottom, or it needs to be loaded in a blocking manner. Otherwise, you get layout shift and potential misclicks.

output.mp4

Copy link
Contributor

@notriddle notriddle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a solution to the layout shift problem.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 23, 2025
@GuillaumeGomez
Copy link
Member Author

Fair enough. I can make the implementors content to be hidden by default (when JS is enabled) and display it when the updated is done.

@GuillaumeGomez
Copy link
Member Author

So now, the implementors lists are only displayed once they have been filled by the JS. I still display the headings though. I also opened the hosted example.

Does that sound good to you @notriddle or do you have something else in mind?

@notriddle
Copy link
Contributor

How does this work on a page like

// It tries to load a JS for each trait but there are none since they're not implemented.
fail-on-request-error: false
that has no foreign impls at all?

@notriddle
Copy link
Contributor

Does that sound good to you @notriddle or do you have something else in mind?

I wanted to put downstream foreign trait impls on the bottom, so we end up equivalent to impls.sort_by_key(|i| (!i.is_foreign, i.is_negative)). That way main.js and trait.Sync.js don't block us from showing content that's already in the HTML anyway.

But, in order of preference, if I have to pick between longer load time and more layout shift, I'll usually pick longer load time. But I'd rather go with an option that avoids both.

@lolbinarycat
Copy link
Contributor

Well, my point was mostly: we have UI saying to the user "generating more content". But in some cases, there might no be. Which then seems like there is a bug (at least it would to me) since something was being loaded and nothing changed.

But if we are hiding all the impls, then there would almost always be something that shows up no?

@GuillaumeGomez
Copy link
Member Author

The section are always present, whether they're empty or not. Currently, content appear (almost) instantly. If we add a visual marker that content is loading and once done, it just disappears because there was nothing to add (ie, no foreign implementors), then it would seem like something failed.

@lolbinarycat
Copy link
Contributor

The section are always present, whether they're empty or not. Currently, content appear (almost) instantly. If we add a visual marker that content is loading and once done, it just disappears because there was nothing to add (ie, no foreign implementors), then it would seem like something failed.

It seems like you still don't understand what I'm saying.

As stated before, one way to prevent layout shifting is to hide all implementations until the foreign impls are loaded. IF we do that, we should also add a loading icon, otherwise every page would initially look like they have no impls.

If a loading indicator into nothing is too confusing, we could add a simple bit of text saying "no implementors" when none are found. (nojs users could even be shown "no local implementors" if we want to be technically correct).

@GuillaumeGomez
Copy link
Member Author

I got what you said. The current implementation of this PR hides all implementations until foreign ones are loaded. You suggest to add a visual indicator that it is loading and I answered to that.

I don't think adding text to indicate the section is empty is very useful either.

@lolbinarycat
Copy link
Contributor

If we add a visual marker that content is loading and once done, it just disappears because there was nothing to add (ie, no foreign implementors), then it would seem like something failed.

You're saying that the indicator would disappear with no changes if there are no foreign implementors, but if we are hiding all implementors, that is incorrect. Such a situation would only happen if there are no implementors at all.

@GuillaumeGomez
Copy link
Member Author

Yep that's my point.

@lolbinarycat
Copy link
Contributor

I think that "not done loading" and "no results" being indistinguishable is bad ui design, and is likely to cause frustration and/or confusion if the loading time is ever significant.

@bors
Copy link
Collaborator

bors commented Sep 27, 2025

☔ The latest upstream changes (presumably #138907) made this pull request unmergeable. Please resolve the merge conflicts.

@rustbot

This comment has been minimized.

@GuillaumeGomez
Copy link
Member Author

So back to this: what do you think about going forward with the current implementation as it improves the current situation and discuss further improvements later on?

@bors
Copy link
Collaborator

bors commented Nov 9, 2025

☔ The latest upstream changes (presumably #148692) made this pull request unmergeable. Please resolve the merge conflicts.

@GuillaumeGomez GuillaumeGomez removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Dec 8, 2025

/// Struct used to handle insertion of "negative impl" marker in the generated DOM.
///
/// This marker appears once in all trait impl lists to divide negative impls from positive impls.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
/// This marker appears once in all trait impl lists to divide negative impls from positive impls.
/// This marker may appear at most once in every trait impl lists to divide negative impls from positive impls.

It might not appear, if item has only negative impls, I think?
And "in all trailt impl lists" makes it sound like it's only gonna appear once on a single HTML page.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, we put it all all the time so JS knows where the separation is.

///
/// This marker appears once in all trait impl lists to divide negative impls from positive impls.
struct NegativeMarker {
inserted_negative_marker: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
inserted_negative_marker: bool,
inserted: bool,

no need to repeat what's being inserted :)


fn insert_if_needed(&mut self, w: &mut fmt::Formatter<'_>, implementor: &Impl) -> fmt::Result {
if !self.inserted_negative_marker && !implementor.is_negative_trait_impl() {
write!(w, "<div class=\"negative-marker\"></div>")?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
write!(w, "<div class=\"negative-marker\"></div>")?;
w.write_str("<div class=\"negative-marker\"></div>")?;

Comment on lines 1161 to 1165
#synthetic-implementors-list, #implementors-list {
/* To prevent layout shift when loading the page with extra implementors being loaded
from JS, we hide the list until it's complete. */
display: none;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nicer to not use el.style.display = 'block' to override this, but rather use classes somehow.

Maybe:

Suggested change
#synthetic-implementors-list, #implementors-list {
/* To prevent layout shift when loading the page with extra implementors being loaded
from JS, we hide the list until it's complete. */
display: none;
}
#synthetic-implementors-list:not(.loaded), #implementors-list:not(.loaded) {
/* To prevent layout shift when loading the page with extra implementors being loaded
from JS, we hide the list until it's complete. */
display: none;
}

and then the "reset" to display: block in noscript.css can be removed (IIUC), and the JS will be something like:

if (implementors[0]) {
    implementors[0].classList.add('loaded');
}
if (syntheticImplementors[0]) {
    syntheticImplementors[0].classList.add('loaded');
}

Your call, but I think it's slightly nicer this way. Also means we don't have to get the default value for display right :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can even add a loaded class on some ancestor element, and then do something like:

.some-parent:not(.loaded) #synthetic-implementors-list, .some-parent:not(.loaded) #implementors-list {
    ...
}

it might come in handy for other "loaded" states we might add later on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep the "loaded" class close to the content which is actually loaded because I imagine we could have other async data load and I'd rather not have to handle that in the JS (checking if all data is loaded).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to precise: I think the loaded class is a good idea. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: we need to keep the noscript part because it will not have the .loaded class (thank you past-me for adding a test for this case T_T).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: we need to keep the noscript part because it will not have the .loaded class (thank you past-me for adding a test for this case T_T).

That's confusing.
Without the .loaded class, the display should default to block.
With noscript, there would be no JS to add that .loaded class in the first place, so I don't understand why we would need to override the display: none with display: block.
I'm probably missing something.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e., this part of the noscript stylesheet is an "oxymoron":
https://github.com/rust-lang/rust/pull/142380/files#diff-6298483d735c3524d041be8e87a755ab53e75cf10dee016939929d3a2e620af5R32-R34

I don't see how it will ever be applicable, noscript and .loaded are two things that should never co-exist.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVM
understood it now
was completely confused there for a second
haha
sorry!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind blown 😆

Took me a few seconds as well to understand what was going on when test failed because it seemed logical that noscript wasn't needed... until I realized. I feel less alone in this "brain misery" now haha

}
}

impl PartialEq for Implementor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not great, at all, that the Eq and PartialEq impls don't behave the same.

I understand that all these impls are just so you're able to sort the implementors, but maybe it would be better to just use sort_unstable_by. Will also be less boilerplate-y, but most importantly more correct. IMHO, at least.

What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah you're right. It was bugging me even when I wrote it so I'm glad it's a shared feeling. Using sort_unstable_by instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(it also allowed to remove a lot of code, so big 👍 for the idea)

@rustbot
Copy link
Collaborator

rustbot commented Dec 10, 2025

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@GuillaumeGomez
Copy link
Member Author

Implemented suggestions.

@rust-log-analyzer

This comment has been minimized.

@yotamofek
Copy link
Contributor

yotamofek commented Dec 10, 2025

Looks really good!

All that's left is my question about the noscript thingy (can maybe be resolved by just explaining to me why I'm wrong 😅),
and
maybe some squashing? The last commit can probably just be part of the previous commits. Not a big deal though, it's fine like this too.

@GuillaumeGomez
Copy link
Member Author

I'll leave it as is if you don't mind. No energy to squash any additional commit today. ^^'

@yotamofek
Copy link
Contributor

@bors r+
Let's not roll this up for the off chance it affects perf

@bors
Copy link
Collaborator

bors commented Dec 10, 2025

📌 Commit fb9d3a5 has been approved by yotamofek

It is now in the queue for this repository.

@bors bors added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Dec 10, 2025
jhpratt added a commit to jhpratt/rust that referenced this pull request Dec 11, 2025
…=yotamofek

Put negative implementors first and apply same ordering logic to foreign implementors

Fixes rust-lang#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`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[rustdoc] Implementors section of Sync (and other similar traits) should separate implementors and !implementors

8 participants