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 macros 2.0 and built-in derives being shown at the wrong path #77862

Merged

Conversation

danielhenrymantilla
Copy link
Contributor

@danielhenrymantilla danielhenrymantilla commented Oct 12, 2020

Fixes #74355

  • waiting on author + draft PR since my code ought to be cleaned up w.r.t. the way I avoid the .unwrap()s:

    • dummy items may avoid the first ?,

    • but within the module traversal some tests did fail (hence the second ?), meaning the crate did not possess the exact path of the containing module (extern / impl blocks maybe? I'll look into that).

r? @jyn514

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Oct 12, 2020
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 12, 2020
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Oct 12, 2020
@rustbot rustbot added the T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. label Oct 12, 2020
@jyn514 jyn514 added the A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) label Oct 12, 2020
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.

This is awful, but I don't know a better way to do it.

src/test/rustdoc/macro_pub_in_module.rs Outdated Show resolved Hide resolved
src/librustdoc/visit_ast.rs Outdated Show resolved Hide resolved
Comment on lines 105 to 109
for path_segment in macro_parent_module.data {
let path_segment = path_segment.to_string();
cur_mod = cur_mod.mods.iter_mut().find(|module| {
matches!(
module.name, Some(symbol)
if symbol.with(|mod_name| mod_name == path_segment)
)
})?;
}
Copy link
Member

Choose a reason for hiding this comment

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

Ugh, I really wish rustdoc didn't use its own data structures (#76382). That said, if this works I won't block on it, but it's unambiguously a hack.

Suggested change
for path_segment in macro_parent_module.data {
let path_segment = path_segment.to_string();
cur_mod = cur_mod.mods.iter_mut().find(|module| {
matches!(
module.name, Some(symbol)
if symbol.with(|mod_name| mod_name == path_segment)
)
})?;
}
// HACK: rustdoc has no way to lookup `doctree::Module`s by their HirId
// HACK: instead, lookup the module by its name, by looking at each path segment one at a time
// WARNING: this will probably break in the presence of re-exports or shadowing.
for path_segment in macro_parent_module.data {
let path_segment = path_segment.to_string();
cur_mod = cur_mod.mods.iter_mut().find(|module| {
matches!(
module.name, Some(symbol)
if symbol.with(|mod_name| mod_name == path_segment)
)
})?;
}

@GuillaumeGomez before I sign off on this, do you know a better way to do this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That said, if this works I won't block on it, but it's unambiguously a hack.

Definitely! The reason I've submitted this code (despite my not having cleaned that try block elsewhere) was for you people to tell me if there was some obvious tool I had missed in places like here, given my lack of experience with rustdoc and rustc internals 👶

@jyn514
Copy link
Member

jyn514 commented Oct 12, 2020

dummy items may avoid the first ?,

Sorry, I wasn't thinking when I said this originally - dummy items are always impls, not macros. A ? wouldn't have worked for fake items, those will ICE instead before the function returns. I'm not sure when parent would return None for a macro.

but within the module traversal some tests did fail (hence the second ?), meaning the crate did not possess the exact path of the containing module (extern / impl blocks maybe? I'll look into that).

Yeah, this will break for macros defined in an impl since those are unnamed. I don't think those macros can be publicly re-exported but I could be wrong.

@danielhenrymantilla danielhenrymantilla marked this pull request as ready for review October 13, 2020 18:27
@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 13, 2020
@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 Oct 14, 2020
src/librustdoc/visit_ast.rs Outdated Show resolved Hide resolved
src/test/rustdoc/auxiliary/macro_pub_in_module.rs Outdated Show resolved Hide resolved
src/test/rustdoc/macro_pub_in_module.rs Outdated Show resolved Hide resolved
src/test/rustdoc/macro_pub_in_module.rs Outdated Show resolved Hide resolved
@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-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 30, 2020
@Dylan-DPC-zz
Copy link

@danielhenrymantilla any updates?

@danielhenrymantilla
Copy link
Contributor Author

danielhenrymantilla commented Nov 1, 2020

@Dylan-DPC still have an issue with the links in the rendered documentation, so I have to fix that before the PR is mergeable. I have been quite busy the last 2 weeks, so I have been unable to make much progress in the meantime, but now I should be able to 🙂

@Dylan-DPC-zz
Copy link

Cool, no issues :)

@bors

This comment has been minimized.

@RalfJung
Copy link
Member

@danielhenrymantilla what is the status of this? #74355 is the last thing blocking the stabilization of #73394, so if there's any way I can help move this forward, please let me know. :) However, I have zero experience working on rustdoc... or even running rustdoc, for that matter.^^

@jyn514
Copy link
Member

jyn514 commented Nov 22, 2020

@RalfJung the review comments need to be addressed, especially the tests.

In general I'm not super happy with the approach this takes, but I don't have a better one in mind, so I'm ok with merging this temporarily until #77828 is fixed.

@Dylan-DPC-zz
Copy link

@danielhenrymantilla any updates?

@jyn514 jyn514 added the A-visibility Area: Visibility / privacy label Jan 7, 2021
@rust-log-analyzer

This comment has been minimized.

}
}
let cur_mod_def_id = tcx.hir().local_def_id(cur_mod.id).to_def_id();
assert_eq!(cur_mod_def_id, macro_parent_def_id);
Copy link
Member

Choose a reason for hiding this comment

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

Now that you added the comment, I think this isn't true in general and there will be times the assertion fails, because cur_mod_def_id is always a module and macro_parent may not be. I think looking for the nearest module is the correct behavior.

Copy link
Contributor Author

@danielhenrymantilla danielhenrymantilla Jan 7, 2021

Choose a reason for hiding this comment

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

Technically that should be caught by the loop logic: if one of the ancestors is not a module, we will be hitting one of the two continue 'exported macros; bail points, and thus never reach that part of the code. That being said, I am open to suggestions to make these things clearer (e.g., we could .try_fold() and Err rather than for + continue 'outer 🤷)

Copy link
Member

Choose a reason for hiding this comment

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

Ok, but wouldn't it be simpler to only look up the parent module in the first place? Then you wouldn't have to skip the segments you don't care about, because you care about all of them.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could add a "fail faster" branch where we'd compare the immediate parent and the parent module, and bail if they're not equal; but even then we'd still need to check that that module in and of itself is reachable / nameable, so most of the logic would still be here. I think the micro optimization is not worth the extra branch; but I keep thinking that a try_fold() for the inner loop may be interesting, since it avoids the need to continue the 'outer loop:

Example (assuming postfix match for the ergonomics)
for def in krate.exported_macros {
    let put_macro_in = |module| module.macros.push((def, None));
    // The `def` of a macro in `exported_macros` should correspond to either:
    //  - a `#[macro_export] macro_rules!` macro,
    //  - a built-in `derive` (or attribute) macro such as the ones in `::core`,
    //  - a `pub macro`.
    // Only the last two need to be fixed, thus:
    if def.ast.macro_rules {
        put_macro_in(&mut top_level_module);
        continue;
    }
    let tcx = self.cx.tcx;
    // Note: this is not the same as `.parent_module()`. Indeed, the latter looks
    // for the closest module _ancestor_, which is not necessarily a direct parent
    // (since a direct parent isn't necessarily a module, c.f. #77828).
    let macro_parent_def_id = {
        use rustc_middle::ty::DefIdTree;
        tcx.parent(tcx.hir().local_def_id(def.hir_id).to_def_id()).unwrap()
    };
    let macro_parent_path = tcx.def_path(macro_parent_def_id);
    // HACK: rustdoc has no way to lookup `doctree::Module`s by their HirId. Instead,
    // lookup the module by its name, by looking at each path segment one at a time.
    macro_parent_path.data.try_fold(&mut top_level_module, |(cur_mod, path_segment)| {
        // Path segments may refer to a module (in which case they belong to the type
        // namespace), which is _necessary_ for the macro to be accessible outside it
        // (no "associated macros" as of yet). Else we bail.
        let path_segment_ty_ns = match path_segment.data {
            rustc_hir::definitions::DefPathData::TypeNs(symbol) => symbol,
            _ => return Err(()),
        };
        // Descend into the child module that matches this path segment, if any.
        cur_mod.mods.iter_mut().find(|child| child.name == Some(path_segment_ty_ns)).ok_or(())
    }).match {
        Ok(module) => {
            let module_def_id =
                tcx.hir().local_def_id(module.id).to_def_id()
            ;
            assert_eq!(module_def_id, macro_parent_def_id);
            put_macro_in(&mut module);
        },
        Err(()) => continue,
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, ok. I'm fine with either the current code or the try_fold.

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.

r=me once Petrochenkov signs off on the resolve changes.

// ignore their containing path to always appear at the crate root.
if md.ast.macro_rules {
self.update(md.hir_id, Some(AccessLevel::Public));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

In theory #[rustc_macro_transparency = "transparent"] pub macro foo { ... } is also possible and needs update called, but that's not a case used by the standard library, and all of this is a hack anyway, so it should be ok for now.

@petrochenkov
Copy link
Contributor

I'm ok with this.
r? @jyn514

@rust-highfive rust-highfive assigned jyn514 and unassigned petrochenkov Jan 9, 2021
@jyn514
Copy link
Member

jyn514 commented Jan 9, 2021

@bors r=jyn514,petrochenkov

@bors
Copy link
Contributor

bors commented Jan 9, 2021

📌 Commit bceb173 has been approved by jyn514,petrochenkov

@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-review Status: Awaiting review from the assignee but also interested parties. labels Jan 9, 2021
@bors
Copy link
Contributor

bors commented Jan 10, 2021

⌛ Testing commit bceb173 with merge 7a19392...

@bors
Copy link
Contributor

bors commented Jan 10, 2021

☀️ Test successful - checks-actions
Approved by: jyn514,petrochenkov
Pushing 7a19392 to master...

@bors bors added the merged-by-bors This PR was explicitly merged by bors. label Jan 10, 2021
@bors bors merged commit 7a19392 into rust-lang:master Jan 10, 2021
@rustbot rustbot added this to the 1.51.0 milestone Jan 10, 2021
@camelid camelid mentioned this pull request Jan 11, 2021
3 tasks
yvt added a commit to r3-os/r3 that referenced this pull request Jan 31, 2021
… fixed

It might have been fixed by [1], but I'm not entirely sure.

[1]: rust-lang/rust#77862
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) A-visibility Area: Visibility / privacy 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.

pub macro defined in submodule is shown at the wrong path
10 participants