Skip to content

Commit c3a99e7

Browse files
committed
Auto merge of rust-lang#136363 - notriddle:notriddle/unresolved-link-unused-refdef, r=GuillaumeGomez
rustdoc: improve refdef handling in the unresolved link lint This commit takes advantage of a feature in pulldown-cmark that makes the list of link definitions available to the consuming application. It produces unresolved link warnings for refdefs that aren't used, and can now produce exact spans for the dest even when it has escapes. Closes rust-lang#133150 since this lint would have caught the mistake in that issue, and, along with rust-lang/rust-clippy#13707, most mistakes in this class should produce a warning from one of them.
2 parents d8810e3 + d15dfe7 commit c3a99e7

File tree

6 files changed

+163
-44
lines changed

6 files changed

+163
-44
lines changed

compiler/rustc_resolve/src/rustdoc.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use pulldown_cmark::{
77
use rustc_ast as ast;
88
use rustc_ast::attr::AttributeExt;
99
use rustc_ast::util::comments::beautify_doc_string;
10-
use rustc_data_structures::fx::FxIndexMap;
10+
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
1111
use rustc_middle::ty::TyCtxt;
1212
use rustc_span::def_id::DefId;
1313
use rustc_span::{DUMMY_SP, InnerSpan, Span, Symbol, kw, sym};
@@ -422,9 +422,11 @@ fn parse_links<'md>(doc: &'md str) -> Vec<Box<str>> {
422422
);
423423
let mut links = Vec::new();
424424

425+
let mut refids = FxHashSet::default();
426+
425427
while let Some(event) = event_iter.next() {
426428
match event {
427-
Event::Start(Tag::Link { link_type, dest_url, title: _, id: _ })
429+
Event::Start(Tag::Link { link_type, dest_url, title: _, id })
428430
if may_be_doc_link(link_type) =>
429431
{
430432
if matches!(
@@ -439,13 +441,25 @@ fn parse_links<'md>(doc: &'md str) -> Vec<Box<str>> {
439441
links.push(display_text);
440442
}
441443
}
444+
if matches!(
445+
link_type,
446+
LinkType::Reference | LinkType::Shortcut | LinkType::Collapsed
447+
) {
448+
refids.insert(id);
449+
}
442450

443451
links.push(preprocess_link(&dest_url));
444452
}
445453
_ => {}
446454
}
447455
}
448456

457+
for (label, refdef) in event_iter.reference_definitions().iter() {
458+
if !refids.contains(label) {
459+
links.push(preprocess_link(&refdef.dest));
460+
}
461+
}
462+
449463
links
450464
}
451465

library/alloc/src/boxed.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,6 @@ impl<T: ?Sized> Box<T> {
10531053
/// ```
10541054
///
10551055
/// [memory layout]: self#memory-layout
1056-
/// [`Layout`]: crate::Layout
10571056
#[stable(feature = "box_raw", since = "1.4.0")]
10581057
#[inline]
10591058
#[must_use = "call `drop(Box::from_raw(ptr))` if you intend to drop the `Box`"]
@@ -1108,7 +1107,6 @@ impl<T: ?Sized> Box<T> {
11081107
/// ```
11091108
///
11101109
/// [memory layout]: self#memory-layout
1111-
/// [`Layout`]: crate::Layout
11121110
#[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
11131111
#[inline]
11141112
#[must_use = "call `drop(Box::from_non_null(ptr))` if you intend to drop the `Box`"]
@@ -1165,7 +1163,6 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
11651163
/// ```
11661164
///
11671165
/// [memory layout]: self#memory-layout
1168-
/// [`Layout`]: crate::Layout
11691166
#[unstable(feature = "allocator_api", issue = "32838")]
11701167
#[rustc_const_unstable(feature = "const_box", issue = "92521")]
11711168
#[inline]
@@ -1219,7 +1216,6 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
12191216
/// ```
12201217
///
12211218
/// [memory layout]: self#memory-layout
1222-
/// [`Layout`]: crate::Layout
12231219
#[unstable(feature = "allocator_api", issue = "32838")]
12241220
// #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
12251221
#[rustc_const_unstable(feature = "const_box", issue = "92521")]

library/core/src/task/wake.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,14 @@ impl RawWaker {
4040
/// of the `vtable` as the first parameter.
4141
///
4242
/// It is important to consider that the `data` pointer must point to a
43-
/// thread safe type such as an `[Arc]<T: Send + Sync>`
43+
/// thread safe type such as an `Arc<T: Send + Sync>`
4444
/// when used to construct a [`Waker`]. This restriction is lifted when
4545
/// constructing a [`LocalWaker`], which allows using types that do not implement
46-
/// <code>[Send] + [Sync]</code> like `[Rc]<T>`.
46+
/// <code>[Send] + [Sync]</code> like `Rc<T>`.
4747
///
4848
/// The `vtable` customizes the behavior of a `Waker` which gets created
4949
/// from a `RawWaker`. For each operation on the `Waker`, the associated
5050
/// function in the `vtable` of the underlying `RawWaker` will be called.
51-
///
52-
/// [`Arc`]: std::sync::Arc
53-
/// [`Rc`]: std::rc::Rc
5451
#[inline]
5552
#[rustc_promotable]
5653
#[stable(feature = "futures_api", since = "1.36.0")]

src/librustdoc/html/markdown.rs

+67-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ use std::sync::{Arc, Weak};
3838
use pulldown_cmark::{
3939
BrokenLink, CodeBlockKind, CowStr, Event, LinkType, Options, Parser, Tag, TagEnd, html,
4040
};
41-
use rustc_data_structures::fx::FxHashMap;
41+
use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
4242
use rustc_errors::{Diag, DiagMessage};
4343
use rustc_hir::def_id::LocalDefId;
4444
use rustc_middle::ty::TyCtxt;
@@ -1763,6 +1763,46 @@ pub(crate) fn markdown_links<'md, R>(
17631763
}
17641764
};
17651765

1766+
let span_for_refdef = |link: &CowStr<'_>, span: Range<usize>| {
1767+
// We want to underline the link's definition, but `span` will point at the entire refdef.
1768+
// Skip the label, then try to find the entire URL.
1769+
let mut square_brace_count = 0;
1770+
let mut iter = md.as_bytes()[span.start..span.end].iter().copied().enumerate();
1771+
for (_i, c) in &mut iter {
1772+
match c {
1773+
b':' if square_brace_count == 0 => break,
1774+
b'[' => square_brace_count += 1,
1775+
b']' => square_brace_count -= 1,
1776+
_ => {}
1777+
}
1778+
}
1779+
while let Some((i, c)) = iter.next() {
1780+
if c == b'<' {
1781+
while let Some((j, c)) = iter.next() {
1782+
match c {
1783+
b'\\' => {
1784+
let _ = iter.next();
1785+
}
1786+
b'>' => {
1787+
return MarkdownLinkRange::Destination(
1788+
i + 1 + span.start..j + span.start,
1789+
);
1790+
}
1791+
_ => {}
1792+
}
1793+
}
1794+
} else if !c.is_ascii_whitespace() {
1795+
while let Some((j, c)) = iter.next() {
1796+
if c.is_ascii_whitespace() {
1797+
return MarkdownLinkRange::Destination(i + span.start..j + span.start);
1798+
}
1799+
}
1800+
return MarkdownLinkRange::Destination(i + span.start..span.end);
1801+
}
1802+
}
1803+
span_for_link(link, span)
1804+
};
1805+
17661806
let span_for_offset_backward = |span: Range<usize>, open: u8, close: u8| {
17671807
let mut open_brace = !0;
17681808
let mut close_brace = !0;
@@ -1844,9 +1884,16 @@ pub(crate) fn markdown_links<'md, R>(
18441884
.into_offset_iter();
18451885
let mut links = Vec::new();
18461886

1887+
let mut refdefs = FxIndexMap::default();
1888+
for (label, refdef) in event_iter.reference_definitions().iter() {
1889+
refdefs.insert(label.to_string(), (false, refdef.dest.to_string(), refdef.span.clone()));
1890+
}
1891+
18471892
for (event, span) in event_iter {
18481893
match event {
1849-
Event::Start(Tag::Link { link_type, dest_url, .. }) if may_be_doc_link(link_type) => {
1894+
Event::Start(Tag::Link { link_type, dest_url, id, .. })
1895+
if may_be_doc_link(link_type) =>
1896+
{
18501897
let range = match link_type {
18511898
// Link is pulled from the link itself.
18521899
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
@@ -1856,7 +1903,12 @@ pub(crate) fn markdown_links<'md, R>(
18561903
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
18571904
// Link is pulled from elsewhere in the document.
18581905
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
1859-
span_for_link(&dest_url, span)
1906+
if let Some((is_used, dest_url, span)) = refdefs.get_mut(&id[..]) {
1907+
*is_used = true;
1908+
span_for_refdef(&CowStr::from(&dest_url[..]), span.clone())
1909+
} else {
1910+
span_for_link(&dest_url, span)
1911+
}
18601912
}
18611913
LinkType::Autolink | LinkType::Email => unreachable!(),
18621914
};
@@ -1873,6 +1925,18 @@ pub(crate) fn markdown_links<'md, R>(
18731925
}
18741926
}
18751927

1928+
for (_label, (is_used, dest_url, span)) in refdefs.into_iter() {
1929+
if !is_used
1930+
&& let Some(link) = preprocess_link(MarkdownLink {
1931+
kind: LinkType::Reference,
1932+
range: span_for_refdef(&CowStr::from(&dest_url[..]), span),
1933+
link: dest_url,
1934+
})
1935+
{
1936+
links.push(link);
1937+
}
1938+
}
1939+
18761940
links
18771941
}
18781942

tests/rustdoc-ui/intra-doc/weird-syntax.rs

+34-9
Original file line numberDiff line numberDiff line change
@@ -117,24 +117,49 @@ pub struct WLinkToCloneWithUnmatchedEscapedCloseParenAndDoubleSpace;
117117

118118
// References
119119

120-
/// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected //~ERROR link
121-
/// in Markdown for not being URL-shaped enough.
122-
///
123-
/// [cln]: Clone() //~ERROR link
120+
/// The [cln][] link here is going to be unresolved, because `Clone()` gets
121+
//~^ ERROR link
122+
/// rejected in Markdown for not being URL-shaped enough.
123+
/// [cln]: Clone()
124+
//~^ ERROR link
124125
pub struct LinkToCloneWithParensInReference;
125126

126-
/// The [cln][] link here is going to be unresolved, because `struct@Clone` gets //~ERROR link
127-
/// rejected in Markdown for not being URL-shaped enough.
127+
/// The [cln][] link here is going to produce a good inline suggestion
128128
///
129-
/// [cln]: struct@Clone //~ERROR link
129+
/// [cln]: struct@Clone
130+
//~^ ERROR link
130131
pub struct LinkToCloneWithWrongPrefix;
131132

132-
/// The [cln][] link here will produce a plain text suggestion //~ERROR link
133+
/// The [cln][] link here will produce a good inline suggestion
133134
///
134135
/// [cln]: Clone\(\)
136+
//~^ ERROR link
135137
pub struct LinkToCloneWithEscapedParensInReference;
136138

137-
/// The [cln][] link here will produce a plain text suggestion //~ERROR link
139+
/// The [cln][] link here will produce a good inline suggestion
138140
///
139141
/// [cln]: struct\@Clone
142+
//~^ ERROR link
140143
pub struct LinkToCloneWithEscapedAtsInReference;
144+
145+
146+
/// This link reference definition isn't used, but since it is still parsed,
147+
/// it should still produce a warning.
148+
///
149+
/// [cln]: struct\@Clone
150+
//~^ ERROR link
151+
pub struct UnusedLinkToCloneReferenceDefinition;
152+
153+
/// <https://github.com/rust-lang/rust/issues/133150>
154+
///
155+
/// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the
156+
//~^ ERROR link
157+
/// `(__unsafe_unretained)` NSWindow associated with the window, if you want
158+
/// to wrap an existing window.
159+
/// - [`SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER`]: the `(__unsafe_unretained)`
160+
/// NSView associated with the window, defaults to `[window contentView]`
161+
pub fn a() {}
162+
#[allow(nonstandard_style)]
163+
pub struct SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER;
164+
#[allow(nonstandard_style)]
165+
pub struct SDL_PROP_WINDOW_CREATE_COCOA_VIEW_POINTER;

tests/rustdoc-ui/intra-doc/weird-syntax.stderr

+44-21
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ LL | /// [w](Clone \))
230230
error: unresolved link to `cln`
231231
--> $DIR/weird-syntax.rs:120:10
232232
|
233-
LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected
233+
LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets
234234
| ^^^ no item named `cln` in scope
235235
|
236236
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
@@ -243,37 +243,60 @@ LL | /// [cln]: Clone()
243243
|
244244
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
245245

246-
error: unresolved link to `cln`
247-
--> $DIR/weird-syntax.rs:126:10
246+
error: incompatible link kind for `Clone`
247+
--> $DIR/weird-syntax.rs:129:12
248248
|
249-
LL | /// The [cln][] link here is going to be unresolved, because `struct@Clone` gets
250-
| ^^^ no item named `cln` in scope
249+
LL | /// [cln]: struct@Clone
250+
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
251251
|
252-
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
252+
help: to link to the trait, prefix with `trait@`
253+
|
254+
LL | /// [cln]: trait@Clone
255+
| ~~~~~~
253256

254-
error: unresolved link to `cln`
255-
--> $DIR/weird-syntax.rs:129:6
257+
error: unresolved link to `Clone`
258+
--> $DIR/weird-syntax.rs:135:12
256259
|
257-
LL | /// [cln]: struct@Clone
258-
| ^^^ no item named `cln` in scope
260+
LL | /// [cln]: Clone\(\)
261+
| ^^^^^^^^^ this link resolves to the trait `Clone`, which is not a function
262+
|
263+
help: to link to the trait, prefix with `trait@`
264+
|
265+
LL - /// [cln]: Clone\(\)
266+
LL + /// [cln]: trait@Clone
259267
|
260-
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
261268

262-
error: unresolved link to `Clone`
263-
--> $DIR/weird-syntax.rs:132:9
269+
error: incompatible link kind for `Clone`
270+
--> $DIR/weird-syntax.rs:141:12
264271
|
265-
LL | /// The [cln][] link here will produce a plain text suggestion
266-
| ^^^^^ this link resolves to the trait `Clone`, which is not a function
272+
LL | /// [cln]: struct\@Clone
273+
| ^^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
274+
|
275+
help: to link to the trait, prefix with `trait@`
276+
|
277+
LL - /// [cln]: struct\@Clone
278+
LL + /// [cln]: trait@struct
267279
|
268-
= help: to link to the trait, prefix with `trait@`: trait@Clone
269280

270281
error: incompatible link kind for `Clone`
271-
--> $DIR/weird-syntax.rs:137:9
282+
--> $DIR/weird-syntax.rs:149:12
272283
|
273-
LL | /// The [cln][] link here will produce a plain text suggestion
274-
| ^^^^^ this link resolved to a trait, which is not a struct
284+
LL | /// [cln]: struct\@Clone
285+
| ^^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
275286
|
276-
= help: to link to the trait, prefix with `trait@`: trait@Clone
287+
help: to link to the trait, prefix with `trait@`
288+
|
289+
LL - /// [cln]: struct\@Clone
290+
LL + /// [cln]: trait@struct
291+
|
292+
293+
error: unresolved link to `the`
294+
--> $DIR/weird-syntax.rs:155:56
295+
|
296+
LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the
297+
| ^^^ no item named `the` in scope
298+
|
299+
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
277300

278-
error: aborting due to 26 previous errors
301+
error: aborting due to 27 previous errors
279302

0 commit comments

Comments
 (0)