Skip to content

Commit 230bb73

Browse files
committed
Auto merge of rust-lang#136363 - notriddle:notriddle/unresolved-link-unused-refdef, r=<try>
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 534d79a + d15dfe7 commit 230bb73

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
@@ -1093,7 +1093,6 @@ impl<T: ?Sized> Box<T> {
10931093
/// ```
10941094
///
10951095
/// [memory layout]: self#memory-layout
1096-
/// [`Layout`]: crate::Layout
10971096
#[stable(feature = "box_raw", since = "1.4.0")]
10981097
#[inline]
10991098
#[must_use = "call `drop(Box::from_raw(ptr))` if you intend to drop the `Box`"]
@@ -1148,7 +1147,6 @@ impl<T: ?Sized> Box<T> {
11481147
/// ```
11491148
///
11501149
/// [memory layout]: self#memory-layout
1151-
/// [`Layout`]: crate::Layout
11521150
#[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
11531151
#[inline]
11541152
#[must_use = "call `drop(Box::from_non_null(ptr))` if you intend to drop the `Box`"]
@@ -1205,7 +1203,6 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
12051203
/// ```
12061204
///
12071205
/// [memory layout]: self#memory-layout
1208-
/// [`Layout`]: crate::Layout
12091206
#[unstable(feature = "allocator_api", issue = "32838")]
12101207
#[rustc_const_unstable(feature = "const_box", issue = "92521")]
12111208
#[inline]
@@ -1259,7 +1256,6 @@ impl<T: ?Sized, A: Allocator> Box<T, A> {
12591256
/// ```
12601257
///
12611258
/// [memory layout]: self#memory-layout
1262-
/// [`Layout`]: crate::Layout
12631259
#[unstable(feature = "allocator_api", issue = "32838")]
12641260
// #[unstable(feature = "box_vec_non_null", reason = "new API", issue = "130364")]
12651261
#[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;
@@ -1762,6 +1762,46 @@ pub(crate) fn markdown_links<'md, R>(
17621762
}
17631763
};
17641764

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

1886+
let mut refdefs = FxIndexMap::default();
1887+
for (label, refdef) in event_iter.reference_definitions().iter() {
1888+
refdefs.insert(label.to_string(), (false, refdef.dest.to_string(), refdef.span.clone()));
1889+
}
1890+
18461891
for (event, span) in event_iter {
18471892
match event {
1848-
Event::Start(Tag::Link { link_type, dest_url, .. }) if may_be_doc_link(link_type) => {
1893+
Event::Start(Tag::Link { link_type, dest_url, id, .. })
1894+
if may_be_doc_link(link_type) =>
1895+
{
18491896
let range = match link_type {
18501897
// Link is pulled from the link itself.
18511898
LinkType::ReferenceUnknown | LinkType::ShortcutUnknown => {
@@ -1855,7 +1902,12 @@ pub(crate) fn markdown_links<'md, R>(
18551902
LinkType::Inline => span_for_offset_backward(span, b'(', b')'),
18561903
// Link is pulled from elsewhere in the document.
18571904
LinkType::Reference | LinkType::Collapsed | LinkType::Shortcut => {
1858-
span_for_link(&dest_url, span)
1905+
if let Some((is_used, dest_url, span)) = refdefs.get_mut(&id[..]) {
1906+
*is_used = true;
1907+
span_for_refdef(&CowStr::from(&dest_url[..]), span.clone())
1908+
} else {
1909+
span_for_link(&dest_url, span)
1910+
}
18591911
}
18601912
LinkType::Autolink | LinkType::Email => unreachable!(),
18611913
};
@@ -1872,6 +1924,18 @@ pub(crate) fn markdown_links<'md, R>(
18721924
}
18731925
}
18741926

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

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
@@ -223,7 +223,7 @@ LL | /// [w](Clone \))
223223
error: unresolved link to `cln`
224224
--> $DIR/weird-syntax.rs:120:10
225225
|
226-
LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets rejected
226+
LL | /// The [cln][] link here is going to be unresolved, because `Clone()` gets
227227
| ^^^ no item named `cln` in scope
228228
|
229229
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
@@ -236,37 +236,60 @@ LL | /// [cln]: Clone()
236236
|
237237
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
238238

239-
error: unresolved link to `cln`
240-
--> $DIR/weird-syntax.rs:126:10
239+
error: incompatible link kind for `Clone`
240+
--> $DIR/weird-syntax.rs:129:12
241241
|
242-
LL | /// The [cln][] link here is going to be unresolved, because `struct@Clone` gets
243-
| ^^^ no item named `cln` in scope
242+
LL | /// [cln]: struct@Clone
243+
| ^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
244244
|
245-
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
245+
help: to link to the trait, prefix with `trait@`
246+
|
247+
LL | /// [cln]: trait@Clone
248+
| ~~~~~~
246249

247-
error: unresolved link to `cln`
248-
--> $DIR/weird-syntax.rs:129:6
250+
error: unresolved link to `Clone`
251+
--> $DIR/weird-syntax.rs:135:12
249252
|
250-
LL | /// [cln]: struct@Clone
251-
| ^^^ no item named `cln` in scope
253+
LL | /// [cln]: Clone\(\)
254+
| ^^^^^^^^^ this link resolves to the trait `Clone`, which is not a function
255+
|
256+
help: to link to the trait, prefix with `trait@`
257+
|
258+
LL - /// [cln]: Clone\(\)
259+
LL + /// [cln]: trait@Clone
252260
|
253-
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
254261

255-
error: unresolved link to `Clone`
256-
--> $DIR/weird-syntax.rs:132:9
262+
error: incompatible link kind for `Clone`
263+
--> $DIR/weird-syntax.rs:141:12
257264
|
258-
LL | /// The [cln][] link here will produce a plain text suggestion
259-
| ^^^^^ this link resolves to the trait `Clone`, which is not a function
265+
LL | /// [cln]: struct\@Clone
266+
| ^^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
267+
|
268+
help: to link to the trait, prefix with `trait@`
269+
|
270+
LL - /// [cln]: struct\@Clone
271+
LL + /// [cln]: trait@struct
260272
|
261-
= help: to link to the trait, prefix with `trait@`: trait@Clone
262273

263274
error: incompatible link kind for `Clone`
264-
--> $DIR/weird-syntax.rs:137:9
275+
--> $DIR/weird-syntax.rs:149:12
265276
|
266-
LL | /// The [cln][] link here will produce a plain text suggestion
267-
| ^^^^^ this link resolved to a trait, which is not a struct
277+
LL | /// [cln]: struct\@Clone
278+
| ^^^^^^^^^^^^^ this link resolved to a trait, which is not a struct
268279
|
269-
= help: to link to the trait, prefix with `trait@`: trait@Clone
280+
help: to link to the trait, prefix with `trait@`
281+
|
282+
LL - /// [cln]: struct\@Clone
283+
LL + /// [cln]: trait@struct
284+
|
285+
286+
error: unresolved link to `the`
287+
--> $DIR/weird-syntax.rs:155:56
288+
|
289+
LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the
290+
| ^^^ no item named `the` in scope
291+
|
292+
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
270293

271-
error: aborting due to 26 previous errors
294+
error: aborting due to 27 previous errors
272295

0 commit comments

Comments
 (0)