Still resolving rustdoc resolution panicking

This commit is contained in:
Kyle Lin 2023-07-01 00:55:37 +08:00
parent 46df95817d
commit 5ce6cc7df3
10 changed files with 90 additions and 80 deletions

View file

@ -410,9 +410,11 @@ fn parse_links<'md>(doc: &'md str) -> Vec<Box<str>> {
while let Some(event) = event_iter.next() { while let Some(event) = event_iter.next() {
match event { match event {
Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => { Event::Start(Tag::Link(link_type, dest, _)) if may_be_doc_link(link_type) => {
if matches!(link_type, LinkType::Inline | LinkType::ReferenceUnknown | LinkType::Reference) {
if let Some(display_text) = collect_link_data(&mut event_iter) { if let Some(display_text) = collect_link_data(&mut event_iter) {
links.push(display_text); links.push(display_text);
} }
}
links.push(preprocess_link(&dest)); links.push(preprocess_link(&dest));
} }

View file

@ -1038,6 +1038,7 @@ impl LinkCollector<'_, '_> {
// resolutions are cached, for other links we want to report an error every // resolutions are cached, for other links we want to report an error every
// time so they are not cached. // time so they are not cached.
matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut), matches!(ori_link.kind, LinkType::Reference | LinkType::Shortcut),
false,
)?; )?;
self.resolve_display_text( self.resolve_display_text(
@ -1232,6 +1233,9 @@ impl LinkCollector<'_, '_> {
// If errors are cached then they are only reported on first occurrence // If errors are cached then they are only reported on first occurrence
// which we want in some cases but not in others. // which we want in some cases but not in others.
cache_errors: bool, cache_errors: bool,
// If this call is intended to be recoverable, then pass true to silence.
// This is only recoverable when path is failed to resolved.
recoverable: bool,
) -> Option<(Res, Option<UrlFragment>)> { ) -> Option<(Res, Option<UrlFragment>)> {
if let Some(res) = self.visited_links.get(&key) { if let Some(res) = self.visited_links.get(&key) {
if res.is_some() || cache_errors { if res.is_some() || cache_errors {
@ -1239,7 +1243,7 @@ impl LinkCollector<'_, '_> {
} }
} }
let mut candidates = self.resolve_with_disambiguator(&key, diag.clone()); let mut candidates = self.resolve_with_disambiguator(&key, diag.clone(), recoverable);
// FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether. // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether.
// However I'm not sure how to check that across crates. // However I'm not sure how to check that across crates.
@ -1290,6 +1294,9 @@ impl LinkCollector<'_, '_> {
&mut self, &mut self,
key: &ResolutionInfo, key: &ResolutionInfo,
diag: DiagnosticInfo<'_>, diag: DiagnosticInfo<'_>,
// If this call is intended to be recoverable, then pass true to silence.
// This is only recoverable when path is failed to resolved.
recoverable: bool,
) -> Vec<(Res, Option<DefId>)> { ) -> Vec<(Res, Option<DefId>)> {
let disambiguator = key.dis; let disambiguator = key.dis;
let path_str = &key.path_str; let path_str = &key.path_str;
@ -1319,7 +1326,9 @@ impl LinkCollector<'_, '_> {
} }
} }
} }
if !recoverable {
resolution_failure(self, diag, path_str, disambiguator, smallvec![err]); resolution_failure(self, diag, path_str, disambiguator, smallvec![err]);
}
return vec![]; return vec![];
} }
} }
@ -1356,6 +1365,7 @@ impl LinkCollector<'_, '_> {
.fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc }); .fold(0, |acc, res| if let Ok(res) = res { acc + res.len() } else { acc });
if len == 0 { if len == 0 {
if !recoverable {
resolution_failure( resolution_failure(
self, self,
diag, diag,
@ -1363,6 +1373,7 @@ impl LinkCollector<'_, '_> {
disambiguator, disambiguator,
candidates.into_iter().filter_map(|res| res.err()).collect(), candidates.into_iter().filter_map(|res| res.err()).collect(),
); );
}
return vec![]; return vec![];
} else if len == 1 { } else if len == 1 {
candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>() candidates.into_iter().filter_map(|res| res.ok()).flatten().collect::<Vec<_>>()
@ -1396,43 +1407,8 @@ impl LinkCollector<'_, '_> {
ori_link: &MarkdownLink, ori_link: &MarkdownLink,
diag_info: &DiagnosticInfo<'_>, diag_info: &DiagnosticInfo<'_>,
) { ) {
// Check if explicit resolution's path is same as resolution of original link's display text path, e.g. // Check if explicit resolution's path is same as resolution of original link's display text path, see
// // tests/rustdoc-ui/lint/redundant_explicit_links.rs for more cases.
// LinkType::Inline:
//
// [target](target)
// [`target`](target)
// [target](path::to::target)
// [`target`](path::to::target)
// [path::to::target](target)
// [`path::to::target`](target)
// [path::to::target](path::to::target)
// [`path::to::target`](path::to::target)
//
// LinkType::ReferenceUnknown
//
// [target][target]
// [`target`][target]
// [target][path::to::target]
// [`target`][path::to::target]
// [path::to::target][target]
// [`path::to::target`][target]
// [path::to::target][path::to::target]
// [`path::to::target`][path::to::target]
//
// LinkType::Reference
//
// [target][target]
// [`target`][target]
// [target][path::to::target]
// [`target`][path::to::target]
// [path::to::target][target]
// [`path::to::target`][target]
// [path::to::target][path::to::target]
// [`path::to::target`][path::to::target]
//
// [target]: target // or [target]: path::to::target
// [path::to::target]: path::to::target // or [path::to::target]: target
// //
// To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated // To avoid disambiguator from panicking, we check if display text path is possible to be disambiguated
// into explicit path. // into explicit path.
@ -1471,6 +1447,7 @@ impl LinkCollector<'_, '_> {
display_res_info, display_res_info,
diag_info.clone(), // this struct should really be Copy, but Range is not :( diag_info.clone(), // this struct should really be Copy, but Range is not :(
false, false,
true,
); );
} }
} }

View file

@ -32,6 +32,13 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) {
return; return;
} }
if item.link_names(&cx.cache).is_empty() {
// If there's no link names in this item,
// then we skip resolution querying to
// avoid from panicking.
return;
}
check_redundant_explicit_link(cx, item, hir_id, &doc); check_redundant_explicit_link(cx, item, hir_id, &doc);
} }
@ -57,7 +64,24 @@ fn check_redundant_explicit_link<'md>(
while let Some((event, link_range)) = offset_iter.next() { while let Some((event, link_range)) = offset_iter.next() {
match event { match event {
Event::Start(Tag::Link(link_type, dest, _)) => match link_type { Event::Start(Tag::Link(link_type, dest, _)) => {
let link_data = collect_link_data(&mut offset_iter);
let explicit_link = dest.to_string();
let display_link = link_data.resolvable_link.clone()?;
let explicit_len = explicit_link.len();
let display_len = display_link.len();
if explicit_len == display_len && explicit_link != display_link {
// Skips if they possibly have no relativity.
continue;
}
if (explicit_len >= display_len
&& &explicit_link[(explicit_len - display_len)..] == display_link)
|| (display_len >= explicit_len
&& &display_link[(display_len - explicit_len)..] == explicit_link) {
match link_type {
LinkType::Inline | LinkType::ReferenceUnknown => { LinkType::Inline | LinkType::ReferenceUnknown => {
check_inline_or_reference_unknown_redundancy( check_inline_or_reference_unknown_redundancy(
cx, cx,
@ -67,7 +91,7 @@ fn check_redundant_explicit_link<'md>(
resolutions, resolutions,
link_range, link_range,
dest.to_string(), dest.to_string(),
collect_link_data(&mut offset_iter), link_data,
if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') }, if link_type == LinkType::Inline { (b'(', b')') } else { (b'[', b']') },
); );
} }
@ -80,10 +104,12 @@ fn check_redundant_explicit_link<'md>(
resolutions, resolutions,
link_range, link_range,
&dest, &dest,
collect_link_data(&mut offset_iter), link_data,
); );
} }
_ => {} _ => {}
}
}
}, },
_ => {} _ => {}
} }

View file

@ -1,6 +1,7 @@
#![deny(rustdoc::unescaped_backticks)] #![deny(rustdoc::unescaped_backticks)]
#![allow(rustdoc::broken_intra_doc_links)] #![allow(rustdoc::broken_intra_doc_links)]
#![allow(rustdoc::invalid_html_tags)] #![allow(rustdoc::invalid_html_tags)]
#![allow(rustdoc::redundant_explicit_links)]
/// ///
pub fn empty() {} pub fn empty() {}

View file

@ -26,5 +26,5 @@ pub fn foo_fn() {}
// @has 'foo/fn.bar_fn.html' '//meta[@name="description"]/@content' \ // @has 'foo/fn.bar_fn.html' '//meta[@name="description"]/@content' \
// 'Description with intra-doc link to foo_fn and [nonexistent_item] and foo_fn.' // 'Description with intra-doc link to foo_fn and [nonexistent_item] and foo_fn.'
#[allow(rustdoc::broken_intra_doc_links)] #[allow(rustdoc::broken_intra_doc_links)]
/// Description with intra-doc link to [foo_fn] and [nonexistent_item] and [foo_fn](self::foo_fn). /// Description with intra-doc link to [foo_fn] and [nonexistent_item] and [foo_fn].
pub fn bar_fn() {} pub fn bar_fn() {}

View file

@ -1,3 +1,5 @@
#![allow(rustdoc::redundant_explicit_links)]
// @has basic/index.html // @has basic/index.html
// @has - '//a/@href' 'struct.ThisType.html' // @has - '//a/@href' 'struct.ThisType.html'
// @has - '//a/@title' 'struct basic::ThisType' // @has - '//a/@title' 'struct basic::ThisType'

View file

@ -1,6 +1,7 @@
// ignore-tidy-linelength // ignore-tidy-linelength
#![crate_name = "foo"] #![crate_name = "foo"]
#![allow(rustdoc::redundant_explicit_links)]
//! Here's a link to [`Vec<T>`] and one to [`Box<Vec<Option<T>>>`]. //! Here's a link to [`Vec<T>`] and one to [`Box<Vec<Option<T>>>`].
//! Here's a link to [`Iterator<Box<T>>::Item`]. //! Here's a link to [`Iterator<Box<T>>::Item`].

View file

@ -1,4 +1,5 @@
#![deny(rustdoc::broken_intra_doc_links)] #![deny(rustdoc::broken_intra_doc_links)]
#![allow(rustdoc::redundant_explicit_links)]
pub struct S; pub struct S;
pub mod char {} pub mod char {}