Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rustdoc: Fix generics generation in search index #88268

Merged
merged 1 commit into from
Oct 30, 2021

Conversation

GuillaumeGomez
Copy link
Member

The generics were not added to the search index as they should, instead they were added as arguments. I used this opportunity to allow generics to have generics themselves (will come in very handy for my current rewrite of the search engine!).

r? @jyn514

@GuillaumeGomez GuillaumeGomez added T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. A-rustdoc-js Area: Rustdoc's JS front-end labels Aug 23, 2021
@rust-highfive
Copy link
Collaborator

Some changes occurred in HTML/CSS/JS.

cc @GuillaumeGomez

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 23, 2021
@jyn514 jyn514 changed the title Fix generics generation in search index rustdoc: Fix generics generation in search index Aug 24, 2021
Copy link
Member

@jyn514 jyn514 left a comment

Choose a reason for hiding this comment

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

I used this opportunity to allow generics to have generics themselves

What does this mean? What code does it allow you to represent? I'm not really clear what any of this code is doing ...

src/librustdoc/formats/item_type.rs Outdated Show resolved Hide resolved
RenderType {
name: get_index_type_name(clean_type, true).map(|s| s.as_str().to_ascii_lowercase()),
generics: get_generics(clean_type),
generics: if generics.is_empty() { None } else { Some(generics) },
Copy link
Member

Choose a reason for hiding this comment

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

Why store an option for this? The caller can check if the Vec is empty, this means it has to check for both None and empty.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because if it's not an option, it'll generate [], which is unneeded. Before to not generate anything then.

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'll add a comment to make it clear.

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused though - can't you just do this same check wherever it would generate an empty Vec? Why does it have to be specifically here?

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'm not sure about the other places... In this case, it's contained inside a vec, so if the vec length is 1 or 2, it's not really a problem. For the other places, it might be different.

1
} else {
0
res.push(TypeWithKind::from((index_ty, ItemType::Primitive)));
Copy link
Member

Choose a reason for hiding this comment

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

Why did you change this bit? How do you know it's a primitive? Can you add an assertion?

Copy link
Member Author

Choose a reason for hiding this comment

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

There is a ty.is_primitive() check just like before. Did I miss something?

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I misread the code. Why did you remove the else branch? What did 0 represent?

Copy link
Member Author

@GuillaumeGomez GuillaumeGomez Aug 24, 2021

Choose a reason for hiding this comment

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

Before, it returned the number of items inserted to know if something needed to be added into the HashSet. Now we add all arguments (as long as they have a name).

So basically, the else branch did nothing and returned value confirming it did nothing.

src/librustdoc/html/render/cache.rs Outdated Show resolved Hide resolved
@GuillaumeGomez
Copy link
Member Author

I used this opportunity to allow generics to have generics themselves

What does this mean? What code does it allow you to represent? I'm not really clear what any of this code is doing ...

Let's take an example then!

struct Foo<A, B>(A, B);

fn foo<A: Display, B: Foo<u32, u64>, C: Foo<A, B>>(arg: A) {}

Like this, arg will be a generic A which itself contains the generics B and C which themselves have generics. Before that, it was all inlined as arguments of the functions, which is at best incorrect.

@GuillaumeGomez GuillaumeGomez force-pushed the generics-search-index branch 3 times, most recently from 843afc5 to 6758aa8 Compare August 24, 2021 13:31
@rust-log-analyzer

This comment has been minimized.

Copy link
Member

@jyn514 jyn514 left a comment

Choose a reason for hiding this comment

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

Is there a reference for how the search index works somewhere? Your examples helped but I have no idea how they'll actually affect search, I don't really feel comfortable signing off on this. Maybe @notriddle or @ollie27 are familiar with search?

1
} else {
0
res.push(TypeWithKind::from((index_ty, ItemType::Primitive)));
Copy link
Member

Choose a reason for hiding this comment

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

Oh, I misread the code. Why did you remove the else branch? What did 0 represent?

src/librustdoc/html/render/cache.rs Outdated Show resolved Hide resolved
// }]
// ```
//
// To be noted that it can work if there is ONLY ONE generic, otherwise we still
Copy link
Member

Choose a reason for hiding this comment

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

Why does it only work if there's a single generic argument? It seems weird to have an outer object that just has name: ""

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's what I tried to explain but I failed I guess haha.

So basically, arguments can only have a type and generics. What happens in case the argument is a full generic which is Display + Debug + Write? Then we can't say it's one type (from a search perspective) but a generic type (with empty name) having three generics.

Copy link
Member

Choose a reason for hiding this comment

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

I just discussed this with Guillaume, and I think I can explain it now.

For purposes of search-indexing, fn foo<T: Display>(x: T) is treated as the type fn(x: Display). This makes a search like trait:Display -> show foo. (It turns out you can filter by item type in rustdoc searches, which is part of what ItemType is for.)

However, fn foo<T: Display + Clone>(x: T)'s argument cannot be reduced into an ItemType::Trait because there are two traits involved, the Display trait and the Clone trait. So instead it's represented as a ItemType::Generic, which means there was more than one bound involved.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. :)

src/librustdoc/json/conversions.rs Outdated Show resolved Hide resolved
RenderType {
name: get_index_type_name(clean_type, true).map(|s| s.as_str().to_ascii_lowercase()),
generics: get_generics(clean_type),
generics: if generics.is_empty() { None } else { Some(generics) },
Copy link
Member

Choose a reason for hiding this comment

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

I'm confused though - can't you just do this same check wherever it would generate an empty Vec? Why does it have to be specifically here?

@rust-log-analyzer

This comment has been minimized.

@GuillaumeGomez
Copy link
Member Author

ping @jyn514

@GuillaumeGomez
Copy link
Member Author

GuillaumeGomez commented Aug 31, 2021

I think @jyn514 is busy and I need this PR to be merged to end the rewrite of the rustdoc search engine so I'll set @camelid as reviewer.

r? @camelid

@rust-highfive rust-highfive assigned camelid and unassigned jyn514 Aug 31, 2021
@camelid
Copy link
Member

camelid commented Sep 2, 2021

I used this opportunity to allow generics to have generics themselves

What does this mean? What code does it allow you to represent? I'm not really clear what any of this code is doing ...

Let's take an example then!

struct Foo<A, B>(A, B);

fn foo<A: Display, B: Foo<u32, u64>, C: Foo<A, B>>(arg: A) {}

Like this, arg will be a generic A which itself contains the generics B and C which themselves have generics. Before that, it was all inlined as arguments of the functions, which is at best incorrect.

I don't really understand this example. Why will A contain B and C? Is it some kind of linked-list/tree structure? And if so, why?

Also, Foo is a struct, so it can't be a trait bound. I assume you meant something like trait Foo<A, B> {}?

How did the search index look previously? Based on your description, it sounds like the type of this function used to be treated as something like fn(Display, Foo<u32, u64>, Foo<A, B>, A)? Is that correct? And what will it look like after this change?

Sorry for all the questions; I'm not very familiar with the search-index code ;)

@GuillaumeGomez
Copy link
Member Author

Woups, I meant that C contains A and B. It should have been:

struct Foo<A, B>(A, B);

fn foo<A: Display, B: Foo<u32, u64>, C: Foo<A, B>>(arg: C) {}

And indeed, it looks like a tree:

arg: [
    {
        type: "Foo",
        generics: [
            {
                type: "Display",
                generics: []
            },
            {
                type: "Foo"
                generics: [
                    {
                        type: "u32",
                        generics: []
                    },
                    {
                        type: "u64",
                        generics: []
                    }
                ]
            }
        ]
    }
]

@@ -48,6 +48,7 @@ crate enum ItemType {
ProcAttribute = 23,
ProcDerive = 24,
TraitAlias = 25,
Generic = 26,
Copy link
Member

Choose a reason for hiding this comment

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

Why are generic parameters treated as items?

Copy link
Member Author

@GuillaumeGomez GuillaumeGomez Sep 2, 2021

Choose a reason for hiding this comment

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

The pure generic ones, like fn foo<T: Display + Debug>(a: T), in here, a cannot be both "Display" and "Debug", it needs to go through a level:

arg: [
  {
    name: "",
    generics: [
      {
        name: "Display"
        generics: []
      },
      {
        name: "Debug"
        generics: []
      }
    ]
  }
]

So in here, the argument with name "" is a "pure" generic, hence creating a new ItemType for it.

Copy link
Member

Choose a reason for hiding this comment

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

But value parameters like a: T don't have item types (at least from looking at the ItemType enum definition), so how are they represented?

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 with an empty name. If they are still present, might be interesting to remove them since they're useless for the search index.

@jyn514
Copy link
Member

jyn514 commented Sep 6, 2021

Oh, sorry, I just saw this ping. Do you still need me to review?

@GuillaumeGomez
Copy link
Member Author

If you have time, it'd very appreciated, otherwise I'll try to help @camelid finish it.

@jyn514
Copy link
Member

jyn514 commented Sep 7, 2021

It sounds like Camelid understands what's going on better than me, I'll leave them assigned.

@bors
Copy link
Contributor

bors commented Oct 25, 2021

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

@bors
Copy link
Contributor

bors commented Oct 27, 2021

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

@GuillaumeGomez
Copy link
Member Author

@notriddle Could you take another look on the rust changes please? :)

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.

Other than the naming thing, I really have only one thing to ask. The code changes, in substance, look reasonable to me.

Can you write some test cases for the JavaScript changes?

@@ -301,6 +326,7 @@ crate fn get_real_types<'tcx>(
}
_ => false,
}) {
let mut gens = Vec::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't a very useful name. Maybe it can instead be something like ty_generics?

@notriddle
Copy link
Contributor

In particular, it doesn't look like there's any rustdoc test cases for fn gamma<T: Two + Traits>(t: T). There should probably be one.

@camelid camelid assigned notriddle and unassigned camelid Oct 29, 2021
@camelid camelid 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 Oct 29, 2021
@GuillaumeGomez
Copy link
Member Author

I added the test you suggested @notriddle (in src/test/rustdoc-js/generics.*) and improved the naming as suggested. :)

@rust-log-analyzer

This comment has been minimized.

@GuillaumeGomez
Copy link
Member Author

@bors: r=notriddle,camelid,jyn514

@bors
Copy link
Contributor

bors commented Oct 29, 2021

📌 Commit 355e6ed has been approved by notriddle,camelid,jyn514

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 29, 2021
@bors
Copy link
Contributor

bors commented Oct 30, 2021

⌛ Testing commit 355e6ed with merge 22f1ad7...

@bors
Copy link
Contributor

bors commented Oct 30, 2021

☀️ Test successful - checks-actions
Approved by: notriddle,camelid,jyn514
Pushing 22f1ad7 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Oct 30, 2021
@bors bors merged commit 22f1ad7 into rust-lang:master Oct 30, 2021
@rustbot rustbot added this to the 1.58.0 milestone Oct 30, 2021
@rust-timer
Copy link
Collaborator

Finished benchmarking commit (22f1ad7): comparison url.

Summary: This benchmark run did not return any relevant changes.

If you disagree with this performance assessment, please file an issue in rust-lang/rustc-perf.

@rustbot label: -perf-regression

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-rustdoc-search Area: Rustdoc's search feature A-type-based-search Area: Searching rustdoc pages using type signatures merged-by-bors This PR was explicitly merged by bors. 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.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants