1
Fork 0

Auto merge of #86662 - mockersf:fix-86620-link-unknown-location, r=jyn514

fix dead link for method in trait of blanket impl from third party crate

fix #86620

* changes `href` method to raise the actual error it had instead of an `Option`
* set the href link correctly in case of an error

I did not manage to make a small reproducer, I think it happens in a situation where
* crate A expose a trait with a blanket impl
* crate B use the trait from crate A
* crate C use types from crate B
* building docs for crate C without dependencies

r? `@jyn514`
This commit is contained in:
bors 2021-07-16 06:44:10 +00:00
commit a6470c7fa8
6 changed files with 71 additions and 31 deletions

View file

@ -459,7 +459,7 @@ impl Item {
.filter_map(|ItemLink { link: s, link_text, did, ref fragment }| { .filter_map(|ItemLink { link: s, link_text, did, ref fragment }| {
match did { match did {
Some(did) => { Some(did) => {
if let Some((mut href, ..)) = href(did.clone(), cx) { if let Ok((mut href, ..)) = href(did.clone(), cx) {
if let Some(ref fragment) = *fragment { if let Some(ref fragment) = *fragment {
href.push('#'); href.push('#');
href.push_str(fragment); href.push_str(fragment);

View file

@ -472,7 +472,19 @@ impl clean::GenericArgs {
} }
} }
crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<String>)> { // Possible errors when computing href link source for a `DefId`
crate enum HrefError {
/// This item is known to rustdoc, but from a crate that does not have documentation generated.
///
/// This can only happen for non-local items.
DocumentationNotBuilt,
/// This can only happen for non-local items when `--document-private-items` is not passed.
Private,
// Not in external cache, href link should be in same page
NotInExternalCache,
}
crate fn href(did: DefId, cx: &Context<'_>) -> Result<(String, ItemType, Vec<String>), HrefError> {
let cache = &cx.cache(); let cache = &cx.cache();
let relative_to = &cx.current; let relative_to = &cx.current;
fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] { fn to_module_fqp(shortty: ItemType, fqp: &[String]) -> &[String] {
@ -480,7 +492,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
} }
if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private { if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private {
return None; return Err(HrefError::Private);
} }
let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) { let (fqp, shortty, mut url_parts) = match cache.paths.get(&did) {
@ -489,22 +501,25 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
href_relative_parts(module_fqp, relative_to) href_relative_parts(module_fqp, relative_to)
}), }),
None => { None => {
let &(ref fqp, shortty) = cache.external_paths.get(&did)?; if let Some(&(ref fqp, shortty)) = cache.external_paths.get(&did) {
let module_fqp = to_module_fqp(shortty, fqp); let module_fqp = to_module_fqp(shortty, fqp);
( (
fqp, fqp,
shortty, shortty,
match cache.extern_locations[&did.krate] { match cache.extern_locations[&did.krate] {
ExternalLocation::Remote(ref s) => { ExternalLocation::Remote(ref s) => {
let s = s.trim_end_matches('/'); let s = s.trim_end_matches('/');
let mut s = vec![&s[..]]; let mut s = vec![&s[..]];
s.extend(module_fqp[..].iter().map(String::as_str)); s.extend(module_fqp[..].iter().map(String::as_str));
s s
} }
ExternalLocation::Local => href_relative_parts(module_fqp, relative_to), ExternalLocation::Local => href_relative_parts(module_fqp, relative_to),
ExternalLocation::Unknown => return None, ExternalLocation::Unknown => return Err(HrefError::DocumentationNotBuilt),
}, },
) )
} else {
return Err(HrefError::NotInExternalCache);
}
} }
}; };
let last = &fqp.last().unwrap()[..]; let last = &fqp.last().unwrap()[..];
@ -518,7 +533,7 @@ crate fn href(did: DefId, cx: &Context<'_>) -> Option<(String, ItemType, Vec<Str
url_parts.push(&filename); url_parts.push(&filename);
} }
} }
Some((url_parts.join("/"), shortty, fqp.to_vec())) Ok((url_parts.join("/"), shortty, fqp.to_vec()))
} }
/// Both paths should only be modules. /// Both paths should only be modules.
@ -567,7 +582,7 @@ fn resolved_path<'a, 'cx: 'a>(
write!(w, "{}{:#}", &last.name, last.args.print(cx))?; write!(w, "{}{:#}", &last.name, last.args.print(cx))?;
} else { } else {
let path = if use_absolute { let path = if use_absolute {
if let Some((_, _, fqp)) = href(did, cx) { if let Ok((_, _, fqp)) = href(did, cx) {
format!( format!(
"{}::{}", "{}::{}",
fqp[..fqp.len() - 1].join("::"), fqp[..fqp.len() - 1].join("::"),
@ -675,7 +690,7 @@ crate fn anchor<'a, 'cx: 'a>(
) -> impl fmt::Display + 'a { ) -> impl fmt::Display + 'a {
let parts = href(did.into(), cx); let parts = href(did.into(), cx);
display_fn(move |f| { display_fn(move |f| {
if let Some((url, short_ty, fqp)) = parts { if let Ok((url, short_ty, fqp)) = parts {
write!( write!(
f, f,
r#"<a class="{}" href="{}" title="{} {}">{}</a>"#, r#"<a class="{}" href="{}" title="{} {}">{}</a>"#,
@ -907,7 +922,7 @@ fn fmt_type<'cx>(
// look at). // look at).
box clean::ResolvedPath { did, .. } => { box clean::ResolvedPath { did, .. } => {
match href(did.into(), cx) { match href(did.into(), cx) {
Some((ref url, _, ref path)) if !f.alternate() => { Ok((ref url, _, ref path)) if !f.alternate() => {
write!( write!(
f, f,
"<a class=\"type\" href=\"{url}#{shortty}.{name}\" \ "<a class=\"type\" href=\"{url}#{shortty}.{name}\" \

View file

@ -62,7 +62,7 @@ use crate::formats::{AssocItemRender, Impl, RenderMode};
use crate::html::escape::Escape; use crate::html::escape::Escape;
use crate::html::format::{ use crate::html::format::{
href, print_abi_with_space, print_constness_with_space, print_default_space, href, print_abi_with_space, print_constness_with_space, print_default_space,
print_generic_bounds, print_where_clause, Buffer, PrintWithSpace, print_generic_bounds, print_where_clause, Buffer, HrefError, PrintWithSpace,
}; };
use crate::html::markdown::{Markdown, MarkdownHtml, MarkdownSummaryLine}; use crate::html::markdown::{Markdown, MarkdownHtml, MarkdownSummaryLine};
@ -856,8 +856,8 @@ fn render_assoc_item(
) { ) {
let name = meth.name.as_ref().unwrap(); let name = meth.name.as_ref().unwrap();
let href = match link { let href = match link {
AssocItemLink::Anchor(Some(ref id)) => format!("#{}", id), AssocItemLink::Anchor(Some(ref id)) => Some(format!("#{}", id)),
AssocItemLink::Anchor(None) => format!("#{}.{}", meth.type_(), name), AssocItemLink::Anchor(None) => Some(format!("#{}.{}", meth.type_(), name)),
AssocItemLink::GotoSource(did, provided_methods) => { AssocItemLink::GotoSource(did, provided_methods) => {
// We're creating a link from an impl-item to the corresponding // We're creating a link from an impl-item to the corresponding
// trait-item and need to map the anchored type accordingly. // trait-item and need to map the anchored type accordingly.
@ -867,9 +867,11 @@ fn render_assoc_item(
ItemType::TyMethod ItemType::TyMethod
}; };
href(did.expect_def_id(), cx) match (href(did.expect_def_id(), cx), ty) {
.map(|p| format!("{}#{}.{}", p.0, ty, name)) (Ok(p), ty) => Some(format!("{}#{}.{}", p.0, ty, name)),
.unwrap_or_else(|| format!("#{}.{}", ty, name)) (Err(HrefError::DocumentationNotBuilt), ItemType::TyMethod) => None,
(Err(_), ty) => Some(format!("#{}.{}", ty, name)),
}
} }
}; };
let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string(); let vis = meth.visibility.print_with_space(meth.def_id, cx).to_string();
@ -904,7 +906,7 @@ fn render_assoc_item(
w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len()); w.reserve(header_len + "<a href=\"\" class=\"fnname\">{".len() + "</a>".len());
write!( write!(
w, w,
"{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a href=\"{href}\" class=\"fnname\">{name}</a>\ "{indent}{vis}{constness}{asyncness}{unsafety}{defaultness}{abi}fn <a {href} class=\"fnname\">{name}</a>\
{generics}{decl}{notable_traits}{where_clause}", {generics}{decl}{notable_traits}{where_clause}",
indent = indent_str, indent = indent_str,
vis = vis, vis = vis,
@ -913,7 +915,8 @@ fn render_assoc_item(
unsafety = unsafety, unsafety = unsafety,
defaultness = defaultness, defaultness = defaultness,
abi = abi, abi = abi,
href = href, // links without a href are valid - https://www.w3schools.com/tags/att_a_href.asp
href = href.map(|href| format!("href=\"{}\"", href)).unwrap_or_else(|| "".to_string()),
name = name, name = name,
generics = g.print(cx), generics = g.print(cx),
decl = d.full_print(header_len, indent, header.asyncness, cx), decl = d.full_print(header_len, indent, header.asyncness, cx),

View file

@ -0,0 +1,11 @@
#![crate_name = "issue_86620_1"]
pub trait VZip {
fn vzip() -> usize;
}
impl<T> VZip for T {
fn vzip() -> usize {
0
}
}

View file

@ -4,4 +4,6 @@
extern crate rustdoc_extern_default_method as ext; extern crate rustdoc_extern_default_method as ext;
// @count extern_default_method/struct.Struct.html '//*[@id="method.provided"]' 1 // @count extern_default_method/struct.Struct.html '//*[@id="method.provided"]' 1
// @has extern_default_method/struct.Struct.html '//div[@id="method.provided"]//a[@class="fnname"]/@href' #method.provided
// @has extern_default_method/struct.Struct.html '//div[@id="method.provided"]//a[@class="anchor"]/@href' #method.provided
pub use ext::Struct; pub use ext::Struct;

View file

@ -0,0 +1,9 @@
// aux-build:issue-86620-1.rs
extern crate issue_86620_1;
use issue_86620_1::*;
// @!has issue_86620/struct.S.html '//div[@id="method.vzip"]//a[@class="fnname"]/@href' #tymethod.vzip
// @has issue_86620/struct.S.html '//div[@id="method.vzip"]//a[@class="anchor"]/@href' #method.vzip
pub struct S;