Skip to content

Commit 23032f3

Browse files
committed
Auto merge of #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 #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 4229b80 + 4d551dd commit 23032f3

File tree

6 files changed

+164
-44
lines changed

6 files changed

+164
-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

+45-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,61 @@ 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 `\]`
253-
254-
error: unresolved link to `cln`
255-
--> $DIR/weird-syntax.rs:129:6
252+
help: to link to the trait, prefix with `trait@`
256253
|
257-
LL | /// [cln]: struct@Clone
258-
| ^^^ no item named `cln` in scope
254+
LL - /// [cln]: struct@Clone
255+
LL + /// [cln]: trait@Clone
259256
|
260-
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
261257

262258
error: unresolved link to `Clone`
263-
--> $DIR/weird-syntax.rs:132:9
259+
--> $DIR/weird-syntax.rs:135:12
260+
|
261+
LL | /// [cln]: Clone\(\)
262+
| ^^^^^^^^^ this link resolves to the trait `Clone`, which is not a function
263+
|
264+
help: to link to the trait, prefix with `trait@`
264265
|
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
266+
LL - /// [cln]: Clone\(\)
267+
LL + /// [cln]: trait@Clone
267268
|
268-
= help: to link to the trait, prefix with `trait@`: trait@Clone
269269

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

278-
error: aborting due to 26 previous errors
302+
error: aborting due to 27 previous errors
279303

0 commit comments

Comments
 (0)