Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
870f848
Add support for trait associated items
GuillaumeGomez Jan 20, 2025
ef39e42
Better handling of paths in link to def feature
GuillaumeGomez Jan 26, 2025
9457a32
Update to last rustc_hir Visitor changes
GuillaumeGomez Jan 26, 2025
687ac3f
Fix panic if an item does not have a body
GuillaumeGomez May 4, 2025
45231fa
[rustdoc] Display unsafe attrs with edition 2024 `unsafe()` wrappers.
obi1kenobi Jul 23, 2025
5ae2d42
get rid of some false negatives in rustdoc::broken_intra_doc_links
lolbinarycat Nov 7, 2024
87d7d80
adjust more unit tests to reflect more aggressive intra-doc linting
lolbinarycat Nov 8, 2024
a7da4b8
rustdoc::broken_intra_doc_links: no backticks = use old behavior
lolbinarycat Apr 18, 2025
0413481
rustdoc: update tests to match new lint behavior
lolbinarycat Apr 18, 2025
6a7d488
rustdoc::broken_intra_doc_links: only be lenient with shortcut links
lolbinarycat Apr 19, 2025
bd85df1
move bad-intra-doc test into intra-doc dir
lolbinarycat Apr 19, 2025
a73d7e3
fix up issues with internal compiler docs revealed by stricter lint
lolbinarycat Jul 24, 2025
0e53d85
Fortify RemoveUnneededDrops test.
cjgillot Jun 16, 2025
ee0118f
[test][AIX] ignore extern_weak linkage test
daltenty Jul 30, 2025
38d5f43
Correctly handle `--no-run` rustdoc test option
GuillaumeGomez Jul 13, 2025
234b8ac
Correctly handle `should_panic` doctest attribute
GuillaumeGomez Jul 7, 2025
631ade8
Add regression test for #143009
GuillaumeGomez Jul 4, 2025
3f7b989
Update std doctests
GuillaumeGomez Jul 7, 2025
c365e6e
Add regression test for #143858
GuillaumeGomez Jul 13, 2025
5aec437
detect infinite recursion with tail calls in ctfe
WaffleLapkin Jul 31, 2025
040f71e
loop match: error on `#[const_continue]` outside `#[loop_match]`
folkertdev Jul 25, 2025
f909918
Add human readable name "Cygwin"
Berrysoft Aug 1, 2025
dbf52ba
Remove the omit_gdb_pretty_printer_section attribute
bjorn3 Jul 31, 2025
61c5343
Add FIXME comments to use `test::ERROR_EXIT_CODE` once public and fix…
GuillaumeGomez Aug 1, 2025
dd9eb3d
Rollup merge of #132748 - lolbinarycat:rustdoc-intra-doc-link-warn-mo…
GuillaumeGomez Aug 1, 2025
5fe6fa4
Rollup merge of #135771 - GuillaumeGomez:jump-to-def-perf, r=fmease
GuillaumeGomez Aug 1, 2025
5b47d0e
Rollup merge of #143360 - folkertdev:const-continue-outside-loop-matc…
GuillaumeGomez Aug 1, 2025
7262030
Rollup merge of #143662 - obi1kenobi:pg/unsafe-attribute-wrappers, r=…
GuillaumeGomez Aug 1, 2025
08058ee
Rollup merge of #143900 - GuillaumeGomez:fix-no-run, r=lolbinarycat,f…
GuillaumeGomez Aug 1, 2025
9d5bd91
Rollup merge of #144614 - cjgillot:fortify-unneeded, r=scottmcm
GuillaumeGomez Aug 1, 2025
7250ce7
Rollup merge of #144703 - daltenty:daltenty/extern-weak, r=Noratrieb
GuillaumeGomez Aug 1, 2025
1888740
Rollup merge of #144738 - bjorn3:remove_omit_gdb_pretty_printer_secti…
GuillaumeGomez Aug 1, 2025
939dee5
Rollup merge of #144756 - WaffleLapkin:inf-rec-etc-ctfe, r=lqd
GuillaumeGomez Aug 1, 2025
55bffb4
Rollup merge of #144766 - Berrysoft:patch-1, r=GuillaumeGomez
GuillaumeGomez Aug 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 74 additions & 46 deletions src/librustdoc/html/highlight.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1059,6 +1059,64 @@ fn string<T: Display, W: Write>(
}
}

fn generate_link_to_def(
out: &mut impl Write,
text_s: &str,
klass: Class,
href_context: &Option<HrefContext<'_, '_>>,
def_span: Span,
open_tag: bool,
) -> bool {
if let Some(href_context) = href_context
&& let Some(href) =
href_context.context.shared.span_correspondence_map.get(&def_span).and_then(|href| {
let context = href_context.context;
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
// one to the documentation page and one to the source definition.
// FIXME: currently, external items only generate a link to their documentation,
// a link to their definition can be generated using this:
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
match href {
LinkFromSrc::Local(span) => {
context.href_from_span_relative(*span, &href_context.current_href)
}
LinkFromSrc::External(def_id) => {
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
.map(|(url, _, _)| url)
}
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
PrimitiveType::primitive_locations(context.tcx())[prim],
context,
Some(href_context.root_path),
)
.ok()
.map(|(url, _, _)| url),
LinkFromSrc::Doc(def_id) => {
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
.map(|(doc_link, _, _)| doc_link)
}
}
})
{
if !open_tag {
// We're already inside an element which has the same klass, no need to give it
// again.
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
} else {
let klass_s = klass.as_html();
if klass_s.is_empty() {
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
} else {
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
}
}
return true;
}
false
}

/// This function writes `text` into `out` with some modifications depending on `klass`:
///
/// * If `klass` is `None`, `text` is written into `out` with no modification.
Expand Down Expand Up @@ -1088,10 +1146,14 @@ fn string_without_closing_tag<T: Display>(
return Some("</span>");
};

let mut added_links = false;
let mut text_s = text.to_string();
if text_s.contains("::") {
let mut span = def_span.with_hi(def_span.lo());
text_s = text_s.split("::").intersperse("::").fold(String::new(), |mut path, t| {
span = span.with_hi(span.hi() + BytePos(t.len() as _));
match t {
"::" => write!(&mut path, "::"),
"self" | "Self" => write!(
&mut path,
"<span class=\"{klass}\">{t}</span>",
Expand All @@ -1104,58 +1166,24 @@ fn string_without_closing_tag<T: Display>(
klass = Class::KeyWord.as_html(),
)
}
t => write!(&mut path, "{t}"),
t => {
if !t.is_empty()
&& generate_link_to_def(&mut path, t, klass, href_context, span, open_tag)
{
added_links = true;
write!(&mut path, "</a>")
} else {
write!(&mut path, "{t}")
}
}
}
.expect("Failed to build source HTML path");
span = span.with_lo(span.lo() + BytePos(t.len() as _));
path
});
}

if let Some(href_context) = href_context
&& let Some(href) = href_context.context.shared.span_correspondence_map.get(&def_span)
&& let Some(href) = {
let context = href_context.context;
// FIXME: later on, it'd be nice to provide two links (if possible) for all items:
// one to the documentation page and one to the source definition.
// FIXME: currently, external items only generate a link to their documentation,
// a link to their definition can be generated using this:
// https://github.com/rust-lang/rust/blob/60f1a2fc4b535ead9c85ce085fdce49b1b097531/src/librustdoc/html/render/context.rs#L315-L338
match href {
LinkFromSrc::Local(span) => {
context.href_from_span_relative(*span, &href_context.current_href)
}
LinkFromSrc::External(def_id) => {
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
.map(|(url, _, _)| url)
}
LinkFromSrc::Primitive(prim) => format::href_with_root_path(
PrimitiveType::primitive_locations(context.tcx())[prim],
context,
Some(href_context.root_path),
)
.ok()
.map(|(url, _, _)| url),
LinkFromSrc::Doc(def_id) => {
format::href_with_root_path(*def_id, context, Some(href_context.root_path))
.ok()
.map(|(doc_link, _, _)| doc_link)
}
}
}
{
if !open_tag {
// We're already inside an element which has the same klass, no need to give it
// again.
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
} else {
let klass_s = klass.as_html();
if klass_s.is_empty() {
write!(out, "<a href=\"{href}\">{text_s}").unwrap();
} else {
write!(out, "<a class=\"{klass_s}\" href=\"{href}\">{text_s}").unwrap();
}
}
if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) {
return Some("</a>");
}
if !open_tag {
Expand Down
118 changes: 72 additions & 46 deletions src/librustdoc/html/render/span_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ use std::path::{Path, PathBuf};

use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
use rustc_hir::intravisit::{self, Visitor};
use rustc_hir::{
ExprKind, HirId, Item, ItemKind, Mod, Node, Pat, PatExpr, PatExprKind, PatKind, QPath,
};
use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId};
use rustc_hir::intravisit::{self, Visitor, VisitorExt};
use rustc_hir::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_span::hygiene::MacroKind;
Expand Down Expand Up @@ -67,7 +65,7 @@ struct SpanMapVisitor<'tcx> {

impl SpanMapVisitor<'_> {
/// This function is where we handle `hir::Path` elements and add them into the "span map".
fn handle_path(&mut self, path: &rustc_hir::Path<'_>) {
fn handle_path(&mut self, path: &rustc_hir::Path<'_>, only_use_last_segment: bool) {
match path.res {
// FIXME: For now, we handle `DefKind` if it's not a `DefKind::TyParam`.
// Would be nice to support them too alongside the other `DefKind`
Expand All @@ -79,24 +77,36 @@ impl SpanMapVisitor<'_> {
LinkFromSrc::External(def_id)
};
// In case the path ends with generics, we remove them from the span.
let span = path
.segments
.last()
.map(|last| {
// In `use` statements, the included item is not in the path segments.
// However, it doesn't matter because you can't have generics on `use`
// statements.
if path.span.contains(last.ident.span) {
path.span.with_hi(last.ident.span.hi())
} else {
path.span
}
})
.unwrap_or(path.span);
let span = if only_use_last_segment
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
{
path_span
} else {
path.segments
.last()
.map(|last| {
// In `use` statements, the included item is not in the path segments.
// However, it doesn't matter because you can't have generics on `use`
// statements.
if path.span.contains(last.ident.span) {
path.span.with_hi(last.ident.span.hi())
} else {
path.span
}
})
.unwrap_or(path.span)
};
self.matches.insert(span, link);
}
Res::Local(_) if let Some(span) = self.tcx.hir_res_span(path.res) => {
self.matches.insert(path.span, LinkFromSrc::Local(clean::Span::new(span)));
let path_span = if only_use_last_segment
&& let Some(path_span) = path.segments.last().map(|segment| segment.ident.span)
{
path_span
} else {
path.span
};
self.matches.insert(path_span, LinkFromSrc::Local(clean::Span::new(span)));
}
Res::PrimTy(p) => {
// FIXME: Doesn't handle "path-like" primitives like arrays or tuples.
Expand Down Expand Up @@ -189,31 +199,17 @@ impl SpanMapVisitor<'_> {
self.matches.insert(span, link);
}
}
}

fn handle_pat(&mut self, p: &Pat<'_>) {
let mut check_qpath = |qpath, hir_id| match qpath {
QPath::TypeRelative(_, path) if matches!(path.res, Res::Err) => {
self.infer_id(path.hir_id, Some(hir_id), qpath.span());
}
QPath::Resolved(_, path) => self.handle_path(path),
_ => {}
};
match p.kind {
PatKind::Binding(_, _, _, Some(p)) => self.handle_pat(p),
PatKind::Struct(qpath, _, _) | PatKind::TupleStruct(qpath, _, _) => {
check_qpath(qpath, p.hir_id)
}
PatKind::Expr(PatExpr { kind: PatExprKind::Path(qpath), hir_id, .. }) => {
check_qpath(*qpath, *hir_id)
}
PatKind::Or(pats) => {
for pat in pats {
self.handle_pat(pat);
}
}
_ => {}
// This is a reimplementation of `hir_enclosing_body_owner` which allows to fail without
// panicking.
fn hir_enclosing_body_owner(tcx: TyCtxt<'_>, hir_id: HirId) -> Option<LocalDefId> {
for (_, node) in tcx.hir_parent_iter(hir_id) {
if let Some((def_id, _)) = node.associated_body() {
return Some(def_id);
}
}
None
}

impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
Expand All @@ -227,12 +223,42 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> {
if self.handle_macro(path.span) {
return;
}
self.handle_path(path);
self.handle_path(path, false);
intravisit::walk_path(self, path);
}

fn visit_pat(&mut self, p: &Pat<'tcx>) {
self.handle_pat(p);
fn visit_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, _span: Span) {
match *qpath {
QPath::TypeRelative(qself, path) => {
if matches!(path.res, Res::Err) {
let tcx = self.tcx;
if let Some(body_id) = hir_enclosing_body_owner(tcx, id) {
let typeck_results = tcx.typeck_body(tcx.hir_body_owned_by(body_id).id());
let path = rustc_hir::Path {
// We change the span to not include parens.
span: path.ident.span,
res: typeck_results.qpath_res(qpath, id),
segments: &[],
};
self.handle_path(&path, false);
}
} else {
self.infer_id(path.hir_id, Some(id), path.ident.span);
}

rustc_ast::visit::try_visit!(self.visit_ty_unambig(qself));
self.visit_path_segment(path);
}
QPath::Resolved(maybe_qself, path) => {
self.handle_path(path, true);

rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself);
if !self.handle_macro(path.span) {
intravisit::walk_path(self, path);
}
}
_ => {}
}
}

fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) {
Expand Down
54 changes: 54 additions & 0 deletions tests/rustdoc/jump-to-def-assoc-items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// This test ensures that patterns also get a link generated.

//@ compile-flags: -Zunstable-options --generate-link-to-definition

#![crate_name = "foo"]

//@ has 'src/foo/jump-to-def-assoc-items.rs.html'

pub trait Trait {
type T;
}
pub trait Another {
type T;
const X: u32;
}

pub struct Foo;

impl Foo {
pub fn new() -> Self { Foo }
}

pub struct C;

impl C {
pub fn wat() {}
}

pub struct Bar;
impl Trait for Bar {
type T = Foo;
}
impl Another for Bar {
type T = C;
const X: u32 = 12;
}

pub fn bar() {
//@ has - '//a[@href="#20"]' 'new'
<Bar as Trait>::T::new();
//@ has - '//a[@href="#26"]' 'wat'
<Bar as Another>::T::wat();

match 12u32 {
//@ has - '//a[@href="#14"]' 'X'
<Bar as Another>::X => {}
_ => {}
}
}

pub struct Far {
//@ has - '//a[@href="#10"]' 'T'
x: <Bar as Trait>::T,
}
24 changes: 24 additions & 0 deletions tests/rustdoc/jump-to-def-ice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// This test ensures that items with no body don't panic when generating
// jump to def links.

//@ compile-flags: -Zunstable-options --generate-link-to-definition

#![crate_name = "foo"]

//@ has 'src/foo/jump-to-def-ice.rs.html'

pub trait A {
type T;
type U;
}

impl A for () {
type T = Self::U;
type U = ();
}

pub trait C {
type X;
}

pub struct F<T: C>(pub T::X);
Loading