From 870f84875b073c7ad84fd943bdcbc0030512bc39 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 20 Jan 2025 16:57:30 +0100 Subject: [PATCH 01/24] Add support for trait associated items --- src/librustdoc/html/render/span_map.rs | 61 ++++++++++++------------ tests/rustdoc/jump-to-def-assoc-items.rs | 15 ++++++ 2 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 tests/rustdoc/jump-to-def-assoc-items.rs diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 846d3ad310ce4..ebbc61db688f7 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -4,9 +4,7 @@ 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::{ExprKind, HirId, Item, ItemKind, Mod, Node, QPath}; use rustc_middle::hir::nested_filter; use rustc_middle::ty::TyCtxt; use rustc_span::hygiene::MacroKind; @@ -189,31 +187,6 @@ 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); - } - } - _ => {} - } - } } impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { @@ -231,8 +204,36 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { 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; + let hir = tcx.hir(); + let body_id = hir.enclosing_body_owner(id); + let typeck_results = tcx.typeck_body(hir.body_owned_by(body_id).id()); + let path = rustc_hir::Path { + // We change the span to not include parens. + span: span.with_hi(path.ident.span.hi()), + res: typeck_results.qpath_res(qpath, id), + segments: &[], + }; + self.handle_path(&path); + } else { + self.infer_id(path.hir_id, Some(id), span); + } + + rustc_ast::visit::try_visit!(self.visit_ty(qself)); + self.visit_path_segment(path); + } + QPath::Resolved(maybe_qself, path) => { + self.handle_path(path); + + rustc_ast::visit::visit_opt!(self, visit_ty, maybe_qself); + self.visit_path(path, id) + } + _ => {} + } } fn visit_mod(&mut self, m: &'tcx Mod<'tcx>, span: Span, id: HirId) { diff --git a/tests/rustdoc/jump-to-def-assoc-items.rs b/tests/rustdoc/jump-to-def-assoc-items.rs new file mode 100644 index 0000000000000..1229c36317253 --- /dev/null +++ b/tests/rustdoc/jump-to-def-assoc-items.rs @@ -0,0 +1,15 @@ +//@ compile-flags: -Zunstable-options --generate-link-to-definition + +#![crate_name = "foo"] + +pub enum Ty { + Var, +} + +//@ has 'src/foo/jump-to-def-assoc-items.rs.html' +//@ has - '//a[@href="#6"]' 'Self::Var' +impl Ty { + fn f() { + let _ = Self::Var; + } +} From ef39e42c17487d2af46d07759cea166b2a72221d Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 26 Jan 2025 21:24:36 +0100 Subject: [PATCH 02/24] Better handling of paths in link to def feature --- src/librustdoc/html/highlight.rs | 120 +++++++++++------- src/librustdoc/html/render/span_map.rs | 70 ++++++---- tests/rustdoc/jump-to-def-assoc-items.rs | 53 +++++++- .../jump-to-def-doc-links-calls.rs | 4 +- tests/rustdoc/jump-to-def/jump-to-def-pats.rs | 10 +- .../jump-to-def/jump-to-non-local-method.rs | 7 +- .../check-source-code-urls-to-def.rs | 2 +- 7 files changed, 175 insertions(+), 91 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 2db1ea8450ce1..db51fced1ef29 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -1052,6 +1052,64 @@ fn string( } } +fn generate_link_to_def( + out: &mut impl Write, + text_s: &str, + klass: Class, + href_context: &Option>, + 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, "{text_s}").unwrap(); + } else { + let klass_s = klass.as_html(); + if klass_s.is_empty() { + write!(out, "{text_s}").unwrap(); + } else { + write!(out, "{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. @@ -1081,10 +1139,14 @@ fn string_without_closing_tag( return Some(""); }; + 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, "{t}", @@ -1097,58 +1159,24 @@ fn string_without_closing_tag( 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, "") + } 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, "{text_s}").unwrap(); - } else { - let klass_s = klass.as_html(); - if klass_s.is_empty() { - write!(out, "{text_s}").unwrap(); - } else { - write!(out, "{text_s}").unwrap(); - } - } + if !added_links && generate_link_to_def(out, &text_s, klass, href_context, def_span, open_tag) { return Some(""); } if !open_tag { diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index ebbc61db688f7..342cf5073f716 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -65,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` @@ -77,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. @@ -200,37 +212,41 @@ 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_qpath(&mut self, qpath: &QPath<'tcx>, id: HirId, span: Span) { + 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; - let hir = tcx.hir(); - let body_id = hir.enclosing_body_owner(id); - let typeck_results = tcx.typeck_body(hir.body_owned_by(body_id).id()); + let body_id = tcx.hir_enclosing_body_owner(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: span.with_hi(path.ident.span.hi()), + span: path.ident.span, res: typeck_results.qpath_res(qpath, id), segments: &[], }; - self.handle_path(&path); + self.handle_path(&path, false); } else { - self.infer_id(path.hir_id, Some(id), span); + self.infer_id(path.hir_id, Some(id), path.ident.span); } - rustc_ast::visit::try_visit!(self.visit_ty(qself)); + if let Some(qself) = qself.try_as_ambig_ty() { + rustc_ast::visit::try_visit!(self.visit_ty(qself)); + } self.visit_path_segment(path); } QPath::Resolved(maybe_qself, path) => { - self.handle_path(path); + self.handle_path(path, true); + let maybe_qself = maybe_qself.and_then(|maybe_qself| maybe_qself.try_as_ambig_ty()); rustc_ast::visit::visit_opt!(self, visit_ty, maybe_qself); - self.visit_path(path, id) + if !self.handle_macro(path.span) { + intravisit::walk_path(self, path); + } } _ => {} } diff --git a/tests/rustdoc/jump-to-def-assoc-items.rs b/tests/rustdoc/jump-to-def-assoc-items.rs index 1229c36317253..8cbf990628386 100644 --- a/tests/rustdoc/jump-to-def-assoc-items.rs +++ b/tests/rustdoc/jump-to-def-assoc-items.rs @@ -1,15 +1,54 @@ +// This test ensures that patterns also get a link generated. + //@ compile-flags: -Zunstable-options --generate-link-to-definition #![crate_name = "foo"] -pub enum Ty { - Var, +//@ has 'src/foo/jump-to-def-assoc-items.rs.html' + +pub trait Trait { + type T; +} +pub trait Another { + type T; + const X: u32; } -//@ has 'src/foo/jump-to-def-assoc-items.rs.html' -//@ has - '//a[@href="#6"]' 'Self::Var' -impl Ty { - fn f() { - let _ = Self::Var; +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' + ::T::new(); + //@ has - '//a[@href="#26"]' 'wat' + ::T::wat(); + + match 12u32 { + //@ has - '//a[@href="#14"]' 'X' + ::X => {} + _ => {} } } + +pub struct Far { + //@ has - '//a[@href="#10"]' 'T' + x: ::T, +} diff --git a/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs b/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs index 618569787733c..55e59f23b6f28 100644 --- a/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs +++ b/tests/rustdoc/jump-to-def/jump-to-def-doc-links-calls.rs @@ -8,7 +8,7 @@ pub struct Bar; impl std::default::Default for Bar { - //@ has - '//a[@href="#20-22"]' 'Self::new' + //@ has - '//a[@href="#20-22"]' 'new' fn default() -> Self { Self::new() } @@ -16,7 +16,7 @@ impl std::default::Default for Bar { //@ has - '//a[@href="#8"]' 'Bar' impl Bar { - //@ has - '//a[@href="#24-26"]' 'Self::bar' + //@ has - '//a[@href="#24-26"]' 'bar' pub fn new()-> Self { Self::bar() } diff --git a/tests/rustdoc/jump-to-def/jump-to-def-pats.rs b/tests/rustdoc/jump-to-def/jump-to-def-pats.rs index 147902b44cf5c..852eba208db01 100644 --- a/tests/rustdoc/jump-to-def/jump-to-def-pats.rs +++ b/tests/rustdoc/jump-to-def/jump-to-def-pats.rs @@ -30,13 +30,13 @@ pub fn foo() -> Result<(), ()> { impl fmt::Display for MyEnum { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - //@ has - '//a[@href="#12"]' 'Self::Ok' + //@ has - '//a[@href="#12"]' 'Ok' Self::Ok(_) => f.write_str("MyEnum::Ok"), - //@ has - '//a[@href="#13"]' 'MyEnum::Err' + //@ has - '//a[@href="#13"]' 'Err' MyEnum::Err(_) => f.write_str("MyEnum::Err"), - //@ has - '//a[@href="#14"]' 'Self::Some' + //@ has - '//a[@href="#14"]' 'Some' Self::Some(_) => f.write_str("MyEnum::Some"), - //@ has - '//a[@href="#15"]' 'Self::None' + //@ has - '//a[@href="#15"]' 'None' Self::None => f.write_str("MyEnum::None"), } } @@ -45,7 +45,7 @@ impl fmt::Display for MyEnum { impl X { fn p(&self) -> &str { match self { - //@ has - '//a[@href="#19"]' 'Self::A' + //@ has - '//a[@href="#19"]' 'A' Self::A => "X::A", } } diff --git a/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs b/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs index e2f530425f0db..1d6d6b8d18faa 100644 --- a/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs +++ b/tests/rustdoc/jump-to-def/jump-to-non-local-method.rs @@ -21,9 +21,10 @@ pub fn bar2(readable: T) { } pub fn bar() { - //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html#method.new"]' 'AtomicIsize::new' + //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html"]' 'AtomicIsize' + //@ has - '//a[@href="{{channel}}/core/sync/atomic/struct.AtomicIsize.html#method.new"]' 'new' let _ = AtomicIsize::new(0); - //@ has - '//a[@href="#48"]' 'local_private' + //@ has - '//a[@href="#49"]' 'local_private' local_private(); } @@ -39,7 +40,7 @@ pub fn macro_call() -> Result<(), ()> { } pub fn variant() { - //@ has - '//a[@href="{{channel}}/core/cmp/enum.Ordering.html#variant.Less"]' 'Ordering::Less' + //@ has - '//a[@href="{{channel}}/core/cmp/enum.Ordering.html#variant.Less"]' 'Less' let _ = Ordering::Less; //@ has - '//a[@href="{{channel}}/core/marker/struct.PhantomData.html"]' 'PhantomData' let _: PhantomData:: = PhantomData; diff --git a/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs b/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs index d701b88bf9fd0..a7b944fa2f6fc 100644 --- a/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs +++ b/tests/rustdoc/source-code-pages/check-source-code-urls-to-def.rs @@ -34,7 +34,7 @@ fn babar() {} // The 5 links to line 23 and the line 23 itself. //@ count - '//pre[@class="rust"]//a[@href="#23"]' 6 //@ has - '//pre[@class="rust"]//a[@href="../../source_code/struct.SourceCode.html"]' \ -// 'source_code::SourceCode' +// 'SourceCode' pub fn foo(a: u32, b: &str, c: String, d: Foo, e: bar::Bar, f: source_code::SourceCode) { let x = 12; let y: Foo = Foo; From 9457a324b4acd11eca0d95a518d173329a23b0c2 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 26 Jan 2025 21:50:50 +0100 Subject: [PATCH 03/24] Update to last rustc_hir Visitor changes --- src/librustdoc/html/render/span_map.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 342cf5073f716..505797ccc0b2f 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -3,7 +3,7 @@ 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::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; @@ -234,16 +234,13 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { self.infer_id(path.hir_id, Some(id), path.ident.span); } - if let Some(qself) = qself.try_as_ambig_ty() { - rustc_ast::visit::try_visit!(self.visit_ty(qself)); - } + 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); - let maybe_qself = maybe_qself.and_then(|maybe_qself| maybe_qself.try_as_ambig_ty()); - rustc_ast::visit::visit_opt!(self, visit_ty, maybe_qself); + rustc_ast::visit::visit_opt!(self, visit_ty_unambig, maybe_qself); if !self.handle_macro(path.span) { intravisit::walk_path(self, path); } From 687ac3fdef85301ba49bbac5230901c35b541270 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 4 May 2025 19:08:23 +0200 Subject: [PATCH 04/24] Fix panic if an item does not have a body --- src/librustdoc/html/render/span_map.rs | 32 ++++++++++++++++++-------- tests/rustdoc/jump-to-def-ice.rs | 24 +++++++++++++++++++ 2 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 tests/rustdoc/jump-to-def-ice.rs diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 505797ccc0b2f..6906e6e30ff86 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -2,7 +2,7 @@ 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::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; @@ -201,6 +201,17 @@ impl SpanMapVisitor<'_> { } } +// 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 { + 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> { type NestedFilter = nested_filter::All; @@ -221,15 +232,16 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { QPath::TypeRelative(qself, path) => { if matches!(path.res, Res::Err) { let tcx = self.tcx; - let body_id = tcx.hir_enclosing_body_owner(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); + 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); } diff --git a/tests/rustdoc/jump-to-def-ice.rs b/tests/rustdoc/jump-to-def-ice.rs new file mode 100644 index 0000000000000..5578b9af3d74f --- /dev/null +++ b/tests/rustdoc/jump-to-def-ice.rs @@ -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(pub T::X); From 45231fa599583edc95843aaa4f23b12f762e01e7 Mon Sep 17 00:00:00 2001 From: Predrag Gruevski Date: Wed, 23 Jul 2025 00:00:01 +0000 Subject: [PATCH 05/24] [rustdoc] Display unsafe attrs with edition 2024 `unsafe()` wrappers. --- src/librustdoc/clean/types.rs | 6 +++--- tests/rustdoc/attribute-rendering.rs | 6 +++--- tests/rustdoc/attributes-2021-edition.rs | 14 ++++++++++++++ tests/rustdoc/attributes-re-export-2021-edition.rs | 13 +++++++++++++ tests/rustdoc/attributes-re-export.rs | 2 +- tests/rustdoc/attributes.rs | 13 +++++++++---- tests/rustdoc/reexport/reexport-attrs.rs | 6 +++--- 7 files changed, 46 insertions(+), 14 deletions(-) create mode 100644 tests/rustdoc/attributes-2021-edition.rs create mode 100644 tests/rustdoc/attributes-re-export-2021-edition.rs diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 5ac5da24299fa..64e707ad9d364 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -768,13 +768,13 @@ impl Item { .iter() .filter_map(|attr| match attr { hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => { - Some(format!("#[link_section = \"{name}\"]")) + Some(format!("#[unsafe(link_section = \"{name}\")]")) } hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => { - Some("#[no_mangle]".to_string()) + Some("#[unsafe(no_mangle)]".to_string()) } hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => { - Some(format!("#[export_name = \"{name}\"]")) + Some(format!("#[unsafe(export_name = \"{name}\")]")) } hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => { Some("#[non_exhaustive]".to_string()) diff --git a/tests/rustdoc/attribute-rendering.rs b/tests/rustdoc/attribute-rendering.rs index 841533814c388..bf9b81077f356 100644 --- a/tests/rustdoc/attribute-rendering.rs +++ b/tests/rustdoc/attribute-rendering.rs @@ -1,7 +1,7 @@ #![crate_name = "foo"] //@ has 'foo/fn.f.html' -//@ has - //*[@'class="rust item-decl"]' '#[export_name = "f"] pub fn f()' -#[export_name = "\ -f"] +//@ has - //*[@'class="rust item-decl"]' '#[unsafe(export_name = "f")] pub fn f()' +#[unsafe(export_name = "\ +f")] pub fn f() {} diff --git a/tests/rustdoc/attributes-2021-edition.rs b/tests/rustdoc/attributes-2021-edition.rs new file mode 100644 index 0000000000000..b5028d8c85254 --- /dev/null +++ b/tests/rustdoc/attributes-2021-edition.rs @@ -0,0 +1,14 @@ +//@ edition: 2021 +#![crate_name = "foo"] + +//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]' +#[no_mangle] +pub extern "C" fn f() {} + +//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "bar")]' +#[export_name = "bar"] +pub extern "C" fn g() {} + +//@ has foo/fn.example.html '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".text")]' +#[link_section = ".text"] +pub extern "C" fn example() {} diff --git a/tests/rustdoc/attributes-re-export-2021-edition.rs b/tests/rustdoc/attributes-re-export-2021-edition.rs new file mode 100644 index 0000000000000..04ee6c273dd61 --- /dev/null +++ b/tests/rustdoc/attributes-re-export-2021-edition.rs @@ -0,0 +1,13 @@ +// Tests that attributes are correctly copied onto a re-exported item. +//@ edition:2024 +#![crate_name = "re_export"] + +//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]' +pub use thingymod::thingy as thingy2; + +mod thingymod { + #[unsafe(no_mangle)] + pub fn thingy() { + + } +} diff --git a/tests/rustdoc/attributes-re-export.rs b/tests/rustdoc/attributes-re-export.rs index 458826ea8a356..820276f83c091 100644 --- a/tests/rustdoc/attributes-re-export.rs +++ b/tests/rustdoc/attributes-re-export.rs @@ -2,7 +2,7 @@ //@ edition:2021 #![crate_name = "re_export"] -//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[no_mangle]' +//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]' pub use thingymod::thingy as thingy2; mod thingymod { diff --git a/tests/rustdoc/attributes.rs b/tests/rustdoc/attributes.rs index e34468a88b113..34487a8912778 100644 --- a/tests/rustdoc/attributes.rs +++ b/tests/rustdoc/attributes.rs @@ -1,13 +1,18 @@ +//@ edition: 2024 #![crate_name = "foo"] -//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[no_mangle]' -#[no_mangle] +//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]' +#[unsafe(no_mangle)] pub extern "C" fn f() {} -//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[export_name = "bar"]' -#[export_name = "bar"] +//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "bar")]' +#[unsafe(export_name = "bar")] pub extern "C" fn g() {} +//@ has foo/fn.example.html '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".text")]' +#[unsafe(link_section = ".text")] +pub extern "C" fn example() {} + //@ has foo/struct.Repr.html '//pre[@class="rust item-decl"]' '#[repr(C, align(8))]' #[repr(C, align(8))] pub struct Repr; diff --git a/tests/rustdoc/reexport/reexport-attrs.rs b/tests/rustdoc/reexport/reexport-attrs.rs index 0ec645841f0b8..aec0a11c0c6a0 100644 --- a/tests/rustdoc/reexport/reexport-attrs.rs +++ b/tests/rustdoc/reexport/reexport-attrs.rs @@ -4,13 +4,13 @@ extern crate reexports_attrs; -//@ has 'foo/fn.f0.html' '//pre[@class="rust item-decl"]' '#[no_mangle]' +//@ has 'foo/fn.f0.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]' pub use reexports_attrs::f0; -//@ has 'foo/fn.f1.html' '//pre[@class="rust item-decl"]' '#[link_section = ".here"]' +//@ has 'foo/fn.f1.html' '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".here")]' pub use reexports_attrs::f1; -//@ has 'foo/fn.f2.html' '//pre[@class="rust item-decl"]' '#[export_name = "f2export"]' +//@ has 'foo/fn.f2.html' '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "f2export")]' pub use reexports_attrs::f2; //@ has 'foo/enum.T0.html' '//pre[@class="rust item-decl"]' '#[repr(u8)]' From 5ae2d42a8c80807c7e3ae7e8aff165fb4d8e046f Mon Sep 17 00:00:00 2001 From: binarycat Date: Thu, 7 Nov 2024 15:33:45 -0600 Subject: [PATCH 06/24] get rid of some false negatives in rustdoc::broken_intra_doc_links rustdoc will not try to do intra-doc linking if the "path" of a link looks too much like a "real url". however, only inline links ([text](url)) can actually contain a url, other types of links (reference links, shortcut links) contain a *reference* which is later resolved to an actual url. the "path" in this case cannot be a url, and therefore it should not be skipped due to looking like a url. Co-authored-by: Michael Howell --- .../passes/collect_intra_doc_links.rs | 13 ++++++-- tests/rustdoc-ui/bad-intra-doc.rs | 13 ++++++++ tests/rustdoc-ui/bad-intra-doc.stderr | 31 +++++++++++++++++++ 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 tests/rustdoc-ui/bad-intra-doc.rs create mode 100644 tests/rustdoc-ui/bad-intra-doc.stderr diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 5a9aa2a94c8b4..97f8a2be10b27 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -941,13 +941,20 @@ fn preprocess_link( ori_link: &MarkdownLink, dox: &str, ) -> Option> { + // certain link kinds cannot have their path be urls, + // so they should not be ignored, no matter how much they look like urls. + // e.g. [https://example.com/] is not a link to example.com. + let can_be_url = ori_link.kind != LinkType::ShortcutUnknown + && ori_link.kind != LinkType::CollapsedUnknown + && ori_link.kind != LinkType::ReferenceUnknown; + // [] is mostly likely not supposed to be a link if ori_link.link.is_empty() { return None; } // Bail early for real links. - if ori_link.link.contains('/') { + if can_be_url && ori_link.link.contains('/') { return None; } @@ -972,7 +979,7 @@ fn preprocess_link( Ok(None) => (None, link, link), Err((err_msg, relative_range)) => { // Only report error if we would not have ignored this link. See issue #83859. - if !should_ignore_link_with_disambiguators(link) { + if !(can_be_url && should_ignore_link_with_disambiguators(link)) { let disambiguator_range = match range_between_backticks(&ori_link.range, dox) { MarkdownLinkRange::Destination(no_backticks_range) => { MarkdownLinkRange::Destination( @@ -989,7 +996,7 @@ fn preprocess_link( } }; - if should_ignore_link(path_str) { + if can_be_url && should_ignore_link(path_str) { return None; } diff --git a/tests/rustdoc-ui/bad-intra-doc.rs b/tests/rustdoc-ui/bad-intra-doc.rs new file mode 100644 index 0000000000000..7bc55756e670b --- /dev/null +++ b/tests/rustdoc-ui/bad-intra-doc.rs @@ -0,0 +1,13 @@ +#![no_std] +#![deny(rustdoc::broken_intra_doc_links)] + +// regression test for https://github.com/rust-lang/rust/issues/54191 + +/// this is not a link to [`example.com`] //~ERROR unresolved link +/// +/// this link [`has spaces in it`]. +/// +/// attempted link to method: [`Foo.bar()`] //~ERROR unresolved link +/// +/// classic broken intra-doc link: [`Bar`] //~ERROR unresolved link +pub struct Foo; diff --git a/tests/rustdoc-ui/bad-intra-doc.stderr b/tests/rustdoc-ui/bad-intra-doc.stderr new file mode 100644 index 0000000000000..9533792b35b60 --- /dev/null +++ b/tests/rustdoc-ui/bad-intra-doc.stderr @@ -0,0 +1,31 @@ +error: unresolved link to `example.com` + --> $DIR/bad-intra-doc.rs:6:29 + | +LL | /// this is not a link to [`example.com`] + | ^^^^^^^^^^^ no item named `example.com` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/bad-intra-doc.rs:2:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `Foo.bar` + --> $DIR/bad-intra-doc.rs:10:33 + | +LL | /// attempted link to method: [`Foo.bar()`] + | ^^^^^^^^^ no item named `Foo.bar` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: unresolved link to `Bar` + --> $DIR/bad-intra-doc.rs:12:38 + | +LL | /// classic broken intra-doc link: [`Bar`] + | ^^^ no item named `Bar` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +error: aborting due to 3 previous errors + From 87d7d80cec060690c9055fa93cb29dfe5eb79ce9 Mon Sep 17 00:00:00 2001 From: binarycat Date: Fri, 8 Nov 2024 13:59:56 -0600 Subject: [PATCH 07/24] adjust more unit tests to reflect more aggressive intra-doc linting --- .../disambiguator-endswith-named-suffix.rs | 30 ++--- ...disambiguator-endswith-named-suffix.stderr | 122 +++++++++++++++++- tests/rustdoc-ui/intra-doc/weird-syntax.rs | 2 +- .../rustdoc-ui/intra-doc/weird-syntax.stderr | 24 ++++ .../lints/redundant_explicit_links-utf8.rs | 14 +- .../redundant_explicit_links-utf8.stderr | 59 +++++++++ 6 files changed, 230 insertions(+), 21 deletions(-) create mode 100644 tests/rustdoc-ui/lints/redundant_explicit_links-utf8.stderr diff --git a/tests/rustdoc-ui/disambiguator-endswith-named-suffix.rs b/tests/rustdoc-ui/disambiguator-endswith-named-suffix.rs index 1174e16dd53fd..cb277d529ce77 100644 --- a/tests/rustdoc-ui/disambiguator-endswith-named-suffix.rs +++ b/tests/rustdoc-ui/disambiguator-endswith-named-suffix.rs @@ -2,67 +2,67 @@ //@ normalize-stderr: "nightly|beta|1\.[0-9][0-9]\.[0-9]" -> "$$CHANNEL" //! [struct@m!()] //~ WARN: unmatched disambiguator `struct` and suffix `!()` -//! [struct@m!{}] +//! [struct@m!{}] //~ WARN: unmatched disambiguator `struct` and suffix `!{}` //! [struct@m![]] //! [struct@f()] //~ WARN: unmatched disambiguator `struct` and suffix `()` //! [struct@m!] //~ WARN: unmatched disambiguator `struct` and suffix `!` //! //! [enum@m!()] //~ WARN: unmatched disambiguator `enum` and suffix `!()` -//! [enum@m!{}] +//! [enum@m!{}] //~ WARN: unmatched disambiguator `enum` and suffix `!{}` //! [enum@m![]] //! [enum@f()] //~ WARN: unmatched disambiguator `enum` and suffix `()` //! [enum@m!] //~ WARN: unmatched disambiguator `enum` and suffix `!` //! //! [trait@m!()] //~ WARN: unmatched disambiguator `trait` and suffix `!()` -//! [trait@m!{}] +//! [trait@m!{}] //~ WARN: unmatched disambiguator `trait` and suffix `!{}` //! [trait@m![]] //! [trait@f()] //~ WARN: unmatched disambiguator `trait` and suffix `()` //! [trait@m!] //~ WARN: unmatched disambiguator `trait` and suffix `!` //! //! [module@m!()] //~ WARN: unmatched disambiguator `module` and suffix `!()` -//! [module@m!{}] +//! [module@m!{}] //~ WARN: unmatched disambiguator `module` and suffix `!{}` //! [module@m![]] //! [module@f()] //~ WARN: unmatched disambiguator `module` and suffix `()` //! [module@m!] //~ WARN: unmatched disambiguator `module` and suffix `!` //! //! [mod@m!()] //~ WARN: unmatched disambiguator `mod` and suffix `!()` -//! [mod@m!{}] +//! [mod@m!{}] //~ WARN: unmatched disambiguator `mod` and suffix `!{}` //! [mod@m![]] //! [mod@f()] //~ WARN: unmatched disambiguator `mod` and suffix `()` //! [mod@m!] //~ WARN: unmatched disambiguator `mod` and suffix `!` //! //! [const@m!()] //~ WARN: unmatched disambiguator `const` and suffix `!()` -//! [const@m!{}] +//! [const@m!{}] //~ WARN: unmatched disambiguator `const` and suffix `!{}` //! [const@m![]] //! [const@f()] //~ WARN: incompatible link kind for `f` //! [const@m!] //~ WARN: unmatched disambiguator `const` and suffix `!` //! //! [constant@m!()] //~ WARN: unmatched disambiguator `constant` and suffix `!()` -//! [constant@m!{}] +//! [constant@m!{}] //~ WARN: unmatched disambiguator `constant` and suffix `!{}` //! [constant@m![]] //! [constant@f()] //~ WARN: incompatible link kind for `f` //! [constant@m!] //~ WARN: unmatched disambiguator `constant` and suffix `!` //! //! [static@m!()] //~ WARN: unmatched disambiguator `static` and suffix `!()` -//! [static@m!{}] +//! [static@m!{}] //~ WARN: unmatched disambiguator `static` and suffix `!{}` //! [static@m![]] //! [static@f()] //~ WARN: incompatible link kind for `f` //! [static@m!] //~ WARN: unmatched disambiguator `static` and suffix `!` //! //! [function@m!()] //~ WARN: unmatched disambiguator `function` and suffix `!()` -//! [function@m!{}] +//! [function@m!{}] //~ WARN: unmatched disambiguator `function` and suffix `!{}` //! [function@m![]] //! [function@f()] //! [function@m!] //~ WARN: unmatched disambiguator `function` and suffix `!` //! //! [fn@m!()] //~ WARN: unmatched disambiguator `fn` and suffix `!()` -//! [fn@m!{}] +//! [fn@m!{}] //~ WARN: unmatched disambiguator `fn` and suffix `!{}` //! [fn@m![]] //! [fn@f()] //! [fn@m!] //~ WARN: unmatched disambiguator `fn` and suffix `!` //! //! [method@m!()] //~ WARN: unmatched disambiguator `method` and suffix `!()` -//! [method@m!{}] +//! [method@m!{}] //~ WARN: unmatched disambiguator `method` and suffix `!{}` //! [method@m![]] //! [method@f()] //! [method@m!] //~ WARN: unmatched disambiguator `method` and suffix `!` @@ -74,13 +74,13 @@ //! [derive@m!] //~ WARN: incompatible link kind for `m` //! //! [type@m!()] //~ WARN: unmatched disambiguator `type` and suffix `!()` -//! [type@m!{}] +//! [type@m!{}] //~ WARN: unmatched disambiguator `type` and suffix `!{}` //! [type@m![]] //! [type@f()] //~ WARN: unmatched disambiguator `type` and suffix `()` //! [type@m!] //~ WARN: unmatched disambiguator `type` and suffix `!` //! //! [value@m!()] //~ WARN: unmatched disambiguator `value` and suffix `!()` -//! [value@m!{}] +//! [value@m!{}] //~ WARN: unmatched disambiguator `value` and suffix `!{}` //! [value@m![]] //! [value@f()] //! [value@m!] //~ WARN: unmatched disambiguator `value` and suffix `!` @@ -92,13 +92,13 @@ //! [macro@m!] //! //! [prim@m!()] //~ WARN: unmatched disambiguator `prim` and suffix `!()` -//! [prim@m!{}] +//! [prim@m!{}] //~ WARN: unmatched disambiguator `prim` and suffix `!{}` //! [prim@m![]] //! [prim@f()] //~ WARN: unmatched disambiguator `prim` and suffix `()` //! [prim@m!] //~ WARN: unmatched disambiguator `prim` and suffix `!` //! //! [primitive@m!()] //~ WARN: unmatched disambiguator `primitive` and suffix `!()` -//! [primitive@m!{}] +//! [primitive@m!{}] //~ WARN: unmatched disambiguator `primitive` and suffix `!{}` //! [primitive@m![]] //! [primitive@f()] //~ WARN: unmatched disambiguator `primitive` and suffix `()` //! [primitive@m!] //~ WARN: unmatched disambiguator `primitive` and suffix `!` diff --git a/tests/rustdoc-ui/disambiguator-endswith-named-suffix.stderr b/tests/rustdoc-ui/disambiguator-endswith-named-suffix.stderr index f4e40a4887383..1840338596880 100644 --- a/tests/rustdoc-ui/disambiguator-endswith-named-suffix.stderr +++ b/tests/rustdoc-ui/disambiguator-endswith-named-suffix.stderr @@ -7,6 +7,14 @@ LL | //! [struct@m!()] = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default +warning: unmatched disambiguator `struct` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:5:6 + | +LL | //! [struct@m!{}] + | ^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `struct` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:7:6 | @@ -31,6 +39,14 @@ LL | //! [enum@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `enum` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:11:6 + | +LL | //! [enum@m!{}] + | ^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `enum` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:13:6 | @@ -55,6 +71,14 @@ LL | //! [trait@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `trait` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:17:6 + | +LL | //! [trait@m!{}] + | ^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `trait` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:19:6 | @@ -79,6 +103,14 @@ LL | //! [module@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `module` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:23:6 + | +LL | //! [module@m!{}] + | ^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `module` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:25:6 | @@ -103,6 +135,14 @@ LL | //! [mod@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `mod` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:29:6 + | +LL | //! [mod@m!{}] + | ^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `mod` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:31:6 | @@ -127,6 +167,14 @@ LL | //! [const@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `const` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:35:6 + | +LL | //! [const@m!{}] + | ^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: incompatible link kind for `f` --> $DIR/disambiguator-endswith-named-suffix.rs:37:6 | @@ -155,6 +203,14 @@ LL | //! [constant@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `constant` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:41:6 + | +LL | //! [constant@m!{}] + | ^^^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: incompatible link kind for `f` --> $DIR/disambiguator-endswith-named-suffix.rs:43:6 | @@ -183,6 +239,14 @@ LL | //! [static@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `static` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:47:6 + | +LL | //! [static@m!{}] + | ^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: incompatible link kind for `f` --> $DIR/disambiguator-endswith-named-suffix.rs:49:6 | @@ -211,6 +275,14 @@ LL | //! [function@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `function` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:53:6 + | +LL | //! [function@m!{}] + | ^^^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `function` and suffix `!` --> $DIR/disambiguator-endswith-named-suffix.rs:56:6 | @@ -227,6 +299,14 @@ LL | //! [fn@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `fn` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:59:6 + | +LL | //! [fn@m!{}] + | ^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `fn` and suffix `!` --> $DIR/disambiguator-endswith-named-suffix.rs:62:6 | @@ -243,6 +323,14 @@ LL | //! [method@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `method` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:65:6 + | +LL | //! [method@m!{}] + | ^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `method` and suffix `!` --> $DIR/disambiguator-endswith-named-suffix.rs:68:6 | @@ -303,6 +391,14 @@ LL | //! [type@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `type` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:77:6 + | +LL | //! [type@m!{}] + | ^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `type` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:79:6 | @@ -327,6 +423,14 @@ LL | //! [value@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `value` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:83:6 + | +LL | //! [value@m!{}] + | ^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `value` and suffix `!` --> $DIR/disambiguator-endswith-named-suffix.rs:86:6 | @@ -351,6 +455,14 @@ LL | //! [prim@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `prim` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:95:6 + | +LL | //! [prim@m!{}] + | ^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `prim` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:97:6 | @@ -375,6 +487,14 @@ LL | //! [primitive@m!()] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators +warning: unmatched disambiguator `primitive` and suffix `!{}` + --> $DIR/disambiguator-endswith-named-suffix.rs:101:6 + | +LL | //! [primitive@m!{}] + | ^^^^^^^^^ + | + = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators + warning: unmatched disambiguator `primitive` and suffix `()` --> $DIR/disambiguator-endswith-named-suffix.rs:103:6 | @@ -391,5 +511,5 @@ LL | //! [primitive@m!] | = note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators -warning: 46 warnings emitted +warning: 61 warnings emitted diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.rs b/tests/rustdoc-ui/intra-doc/weird-syntax.rs index d2a922b2b6247..1c5977b1bc33d 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.rs +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.rs @@ -65,7 +65,7 @@ pub struct XLinkToCloneWithStartSpace; /// [x][struct@Clone ] //~ERROR link pub struct XLinkToCloneWithEndSpace; -/// [x][Clone\(\)] not URL-shaped enough +/// [x][Clone\(\)] //~ERROR link pub struct XLinkToCloneWithEscapedParens; /// [x][`Clone`] not URL-shaped enough diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr index ad813f0f9b623..3a567e409eff7 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr @@ -123,6 +123,14 @@ LL - /// [x][struct@Clone ] LL + /// [x][trait@Clone ] | +error: unresolved link to `Clone\(\)` + --> $DIR/weird-syntax.rs:68:9 + | +LL | /// [x][Clone\(\)] + | ^^^^^^^^^ no item named `Clone\(\)` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + error: unresolved link to `Clone` --> $DIR/weird-syntax.rs:74:9 | @@ -299,5 +307,21 @@ LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +error: unresolved link to `Clone` + --> $DIR/weird-syntax.rs:132:9 + | +LL | /// The [cln][] link here will produce a plain text suggestion + | ^^^^^ this link resolves to the trait `Clone`, which is not a function + | + = help: to link to the trait, prefix with `trait@`: trait@Clone + +error: incompatible link kind for `Clone` + --> $DIR/weird-syntax.rs:137:9 + | +LL | /// The [cln][] link here will produce a plain text suggestion + | ^^^^^ this link resolved to a trait, which is not a struct + | + = help: to link to the trait, prefix with `trait@`: trait@Clone + error: aborting due to 27 previous errors diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.rs b/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.rs index 4f4590d45fc88..81cd6c73d1c17 100644 --- a/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.rs +++ b/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.rs @@ -1,18 +1,24 @@ //@ check-pass -/// [`…foo`] [`…bar`] [`Err`] +/// [`…foo`] //~ WARN: unresolved link +/// [`…bar`] //~ WARN: unresolved link +/// [`Err`] pub struct Broken {} -/// [`…`] [`…`] [`Err`] +/// [`…`] //~ WARN: unresolved link +/// [`…`] //~ WARN: unresolved link +/// [`Err`] pub struct Broken2 {} -/// [`…`][…] [`…`][…] [`Err`] +/// [`…`][…] //~ WARN: unresolved link +/// [`…`][…] //~ WARN: unresolved link +/// [`Err`] pub struct Broken3 {} /// […………………………][Broken3] pub struct Broken4 {} -/// [Broken3][…………………………] +/// [Broken3][…………………………] //~ WARN: unresolved link pub struct Broken5 {} pub struct Err; diff --git a/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.stderr b/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.stderr new file mode 100644 index 0000000000000..0a5ff68b90814 --- /dev/null +++ b/tests/rustdoc-ui/lints/redundant_explicit_links-utf8.stderr @@ -0,0 +1,59 @@ +warning: unresolved link to `…foo` + --> $DIR/redundant_explicit_links-utf8.rs:3:7 + | +LL | /// [`…foo`] + | ^^^^ no item named `…foo` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + = note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default + +warning: unresolved link to `…bar` + --> $DIR/redundant_explicit_links-utf8.rs:4:7 + | +LL | /// [`…bar`] + | ^^^^ no item named `…bar` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: unresolved link to `…` + --> $DIR/redundant_explicit_links-utf8.rs:8:7 + | +LL | /// [`…`] + | ^ no item named `…` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: unresolved link to `…` + --> $DIR/redundant_explicit_links-utf8.rs:9:7 + | +LL | /// [`…`] + | ^ no item named `…` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: unresolved link to `…` + --> $DIR/redundant_explicit_links-utf8.rs:13:11 + | +LL | /// [`…`][…] + | ^ no item named `…` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: unresolved link to `…` + --> $DIR/redundant_explicit_links-utf8.rs:14:11 + | +LL | /// [`…`][…] + | ^ no item named `…` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: unresolved link to `…………………………` + --> $DIR/redundant_explicit_links-utf8.rs:21:15 + | +LL | /// [Broken3][…………………………] + | ^^^^^^^^^^ no item named `…………………………` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + +warning: 7 warnings emitted + From a7da4b829d328f129e342917908b70c5f4b98abc Mon Sep 17 00:00:00 2001 From: binarycat Date: Fri, 18 Apr 2025 15:32:56 -0500 Subject: [PATCH 08/24] rustdoc::broken_intra_doc_links: no backticks = use old behavior this is in an effort to reduce the amount of code churn caused by this lint triggering on text that was never meant to be a link. a more principled hierustic for ignoring lints is not possible without extensive changes, due to the lint emitting code being so far away from the link collecting code, and the fact that only the link collecting code has access to details about how the link appears in the unnormalized markdown. --- src/librustdoc/passes/collect_intra_doc_links.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 97f8a2be10b27..08f0d4015fcaf 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -996,7 +996,9 @@ fn preprocess_link( } }; - if can_be_url && should_ignore_link(path_str) { + // If there's no backticks, be lenient revert to old behavior. + // This is to prevent churn by linting on stuff that isn't meant to be a link. + if (can_be_url || !ori_link.link.contains('`')) && should_ignore_link(path_str) { return None; } From 041348110e2e526a80efd5adfc2909c716cbb37f Mon Sep 17 00:00:00 2001 From: binarycat Date: Fri, 18 Apr 2025 17:15:11 -0500 Subject: [PATCH 09/24] rustdoc: update tests to match new lint behavior --- tests/rustdoc-ui/bad-intra-doc.rs | 2 ++ tests/rustdoc-ui/intra-doc/weird-syntax.rs | 2 +- .../rustdoc-ui/intra-doc/weird-syntax.stderr | 24 ------------------- 3 files changed, 3 insertions(+), 25 deletions(-) diff --git a/tests/rustdoc-ui/bad-intra-doc.rs b/tests/rustdoc-ui/bad-intra-doc.rs index 7bc55756e670b..c24a848d8985c 100644 --- a/tests/rustdoc-ui/bad-intra-doc.rs +++ b/tests/rustdoc-ui/bad-intra-doc.rs @@ -10,4 +10,6 @@ /// attempted link to method: [`Foo.bar()`] //~ERROR unresolved link /// /// classic broken intra-doc link: [`Bar`] //~ERROR unresolved link +/// +/// no backticks, so we let this one slide: [Foo.bar()] pub struct Foo; diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.rs b/tests/rustdoc-ui/intra-doc/weird-syntax.rs index 1c5977b1bc33d..9f18f67fc44ab 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.rs +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.rs @@ -65,7 +65,7 @@ pub struct XLinkToCloneWithStartSpace; /// [x][struct@Clone ] //~ERROR link pub struct XLinkToCloneWithEndSpace; -/// [x][Clone\(\)] //~ERROR link +/// [x][Clone\(\)] pub struct XLinkToCloneWithEscapedParens; /// [x][`Clone`] not URL-shaped enough diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr index 3a567e409eff7..ad813f0f9b623 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr @@ -123,14 +123,6 @@ LL - /// [x][struct@Clone ] LL + /// [x][trait@Clone ] | -error: unresolved link to `Clone\(\)` - --> $DIR/weird-syntax.rs:68:9 - | -LL | /// [x][Clone\(\)] - | ^^^^^^^^^ no item named `Clone\(\)` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` - error: unresolved link to `Clone` --> $DIR/weird-syntax.rs:74:9 | @@ -307,21 +299,5 @@ LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -error: unresolved link to `Clone` - --> $DIR/weird-syntax.rs:132:9 - | -LL | /// The [cln][] link here will produce a plain text suggestion - | ^^^^^ this link resolves to the trait `Clone`, which is not a function - | - = help: to link to the trait, prefix with `trait@`: trait@Clone - -error: incompatible link kind for `Clone` - --> $DIR/weird-syntax.rs:137:9 - | -LL | /// The [cln][] link here will produce a plain text suggestion - | ^^^^^ this link resolved to a trait, which is not a struct - | - = help: to link to the trait, prefix with `trait@`: trait@Clone - error: aborting due to 27 previous errors From 6a7d4882a207890c75ca09ce19810d90ac9cfde6 Mon Sep 17 00:00:00 2001 From: binarycat Date: Fri, 18 Apr 2025 20:23:05 -0500 Subject: [PATCH 10/24] rustdoc::broken_intra_doc_links: only be lenient with shortcut links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit collapsed links and reference links have a pretty particular syntax, it seems unlikely they would show up on accident. Co-authored-by: León Orell Valerian Liehr --- .../passes/collect_intra_doc_links.rs | 27 +++++++++++++++---- tests/rustdoc-ui/intra-doc/weird-syntax.rs | 2 +- .../rustdoc-ui/intra-doc/weird-syntax.stderr | 10 ++++++- 3 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 08f0d4015fcaf..c9fa3a4837f29 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -944,9 +944,10 @@ fn preprocess_link( // certain link kinds cannot have their path be urls, // so they should not be ignored, no matter how much they look like urls. // e.g. [https://example.com/] is not a link to example.com. - let can_be_url = ori_link.kind != LinkType::ShortcutUnknown - && ori_link.kind != LinkType::CollapsedUnknown - && ori_link.kind != LinkType::ReferenceUnknown; + let can_be_url = !matches!( + ori_link.kind, + LinkType::ShortcutUnknown | LinkType::CollapsedUnknown | LinkType::ReferenceUnknown + ); // [] is mostly likely not supposed to be a link if ori_link.link.is_empty() { @@ -996,9 +997,25 @@ fn preprocess_link( } }; - // If there's no backticks, be lenient revert to old behavior. + // If there's no backticks, be lenient and revert to the old behavior. // This is to prevent churn by linting on stuff that isn't meant to be a link. - if (can_be_url || !ori_link.link.contains('`')) && should_ignore_link(path_str) { + // only shortcut links have simple enough syntax that they + // are likely to be written accidentally, collapsed and reference links + // need 4 metachars, and reference links will not usually use + // backticks in the reference name. + // therefore, only shortcut syntax gets the lenient behavior. + // + // here's a truth table for how link kinds that cannot be urls are handled: + // + // |-------------------------------------------------------| + // | | is shortcut link | not shortcut link | + // |--------------|--------------------|-------------------| + // | has backtick | never ignore | never ignore | + // | no backtick | ignore if url-like | never ignore | + // |-------------------------------------------------------| + let ignore_urllike = + can_be_url || (ori_link.kind == LinkType::ShortcutUnknown && !ori_link.link.contains('`')); + if ignore_urllike && should_ignore_link(path_str) { return None; } diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.rs b/tests/rustdoc-ui/intra-doc/weird-syntax.rs index 9f18f67fc44ab..1c5977b1bc33d 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.rs +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.rs @@ -65,7 +65,7 @@ pub struct XLinkToCloneWithStartSpace; /// [x][struct@Clone ] //~ERROR link pub struct XLinkToCloneWithEndSpace; -/// [x][Clone\(\)] +/// [x][Clone\(\)] //~ERROR link pub struct XLinkToCloneWithEscapedParens; /// [x][`Clone`] not URL-shaped enough diff --git a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr index ad813f0f9b623..7f2fc1fe62562 100644 --- a/tests/rustdoc-ui/intra-doc/weird-syntax.stderr +++ b/tests/rustdoc-ui/intra-doc/weird-syntax.stderr @@ -123,6 +123,14 @@ LL - /// [x][struct@Clone ] LL + /// [x][trait@Clone ] | +error: unresolved link to `Clone\(\)` + --> $DIR/weird-syntax.rs:68:9 + | +LL | /// [x][Clone\(\)] + | ^^^^^^^^^ no item named `Clone\(\)` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` + error: unresolved link to `Clone` --> $DIR/weird-syntax.rs:74:9 | @@ -299,5 +307,5 @@ LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the | = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -error: aborting due to 27 previous errors +error: aborting due to 28 previous errors From bd85df192d07660511b711cd7d4cae5b5a85577a Mon Sep 17 00:00:00 2001 From: binarycat Date: Sat, 19 Apr 2025 10:51:40 -0500 Subject: [PATCH 11/24] move bad-intra-doc test into intra-doc dir --- tests/rustdoc-ui/{ => intra-doc}/bad-intra-doc.rs | 0 tests/rustdoc-ui/{ => intra-doc}/bad-intra-doc.stderr | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename tests/rustdoc-ui/{ => intra-doc}/bad-intra-doc.rs (100%) rename tests/rustdoc-ui/{ => intra-doc}/bad-intra-doc.stderr (100%) diff --git a/tests/rustdoc-ui/bad-intra-doc.rs b/tests/rustdoc-ui/intra-doc/bad-intra-doc.rs similarity index 100% rename from tests/rustdoc-ui/bad-intra-doc.rs rename to tests/rustdoc-ui/intra-doc/bad-intra-doc.rs diff --git a/tests/rustdoc-ui/bad-intra-doc.stderr b/tests/rustdoc-ui/intra-doc/bad-intra-doc.stderr similarity index 100% rename from tests/rustdoc-ui/bad-intra-doc.stderr rename to tests/rustdoc-ui/intra-doc/bad-intra-doc.stderr From a73d7e3ab9cc29cb427c1463d1a02b89dd7728b6 Mon Sep 17 00:00:00 2001 From: binarycat Date: Thu, 24 Jul 2025 12:37:46 -0500 Subject: [PATCH 12/24] fix up issues with internal compiler docs revealed by stricter lint --- compiler/rustc_mir_transform/src/elaborate_drop.rs | 4 ++++ compiler/rustc_thread_pool/src/sleep/mod.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_mir_transform/src/elaborate_drop.rs b/compiler/rustc_mir_transform/src/elaborate_drop.rs index de96b1f255a6e..de60772b17ea2 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drop.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drop.rs @@ -611,6 +611,7 @@ where /// /// For example, with 3 fields, the drop ladder is /// + /// ```text /// .d0: /// ELAB(drop location.0 [target=.d1, unwind=.c1]) /// .d1: @@ -621,8 +622,10 @@ where /// ELAB(drop location.1 [target=.c2]) /// .c2: /// ELAB(drop location.2 [target=`self.unwind`]) + /// ``` /// /// For possible-async drops in coroutines we also need dropline ladder + /// ```text /// .d0 (mainline): /// ELAB(drop location.0 [target=.d1, unwind=.c1, drop=.e1]) /// .d1 (mainline): @@ -637,6 +640,7 @@ where /// ELAB(drop location.1 [target=.e2, unwind=.c2]) /// .e2 (dropline): /// ELAB(drop location.2 [target=`self.drop`, unwind=`self.unwind`]) + /// ``` /// /// NOTE: this does not clear the master drop flag, so you need /// to point succ/unwind on a `drop_ladder_bottom`. diff --git a/compiler/rustc_thread_pool/src/sleep/mod.rs b/compiler/rustc_thread_pool/src/sleep/mod.rs index 31bf7184b42d5..aa6666092147a 100644 --- a/compiler/rustc_thread_pool/src/sleep/mod.rs +++ b/compiler/rustc_thread_pool/src/sleep/mod.rs @@ -44,7 +44,7 @@ impl SleepData { /// jobs are published, and it either blocks threads or wakes them in response to these /// events. See the [`README.md`] in this module for more details. /// -/// [`README.md`] README.md +/// [`README.md`]: README.md pub(super) struct Sleep { /// One "sleep state" per worker. Used to track if a worker is sleeping and to have /// them block. From 0e53d8533bf0bb8d6bc59bb000fe7cc7a361902e Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Mon, 16 Jun 2025 15:25:31 +0000 Subject: [PATCH 13/24] Fortify RemoveUnneededDrops test. --- ...annot_opt_generic.RemoveUnneededDrops.diff | 15 +++++++ ...neric.RemoveUnneededDrops.panic-abort.diff | 26 ------------ ...eric.RemoveUnneededDrops.panic-unwind.diff | 30 -------------- ...ed_drops.dont_opt.RemoveUnneededDrops.diff | 15 +++++++ ...t_opt.RemoveUnneededDrops.panic-abort.diff | 26 ------------ ..._opt.RemoveUnneededDrops.panic-unwind.diff | 30 -------------- ...nneeded_drops.opt.RemoveUnneededDrops.diff | 15 +++++++ ...s.opt.RemoveUnneededDrops.panic-abort.diff | 26 ------------ ....opt.RemoveUnneededDrops.panic-unwind.diff | 26 ------------ ....opt_generic_copy.RemoveUnneededDrops.diff | 15 +++++++ ..._copy.RemoveUnneededDrops.panic-abort.diff | 26 ------------ ...copy.RemoveUnneededDrops.panic-unwind.diff | 26 ------------ tests/mir-opt/remove_unneeded_drops.rs | 40 ++++++++++++++++--- 13 files changed, 94 insertions(+), 222 deletions(-) create mode 100644 tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-abort.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-unwind.diff create mode 100644 tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-abort.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-unwind.diff create mode 100644 tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-abort.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-unwind.diff create mode 100644 tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-abort.diff delete mode 100644 tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-unwind.diff diff --git a/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff b/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff new file mode 100644 index 0000000000000..52832e739051d --- /dev/null +++ b/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff @@ -0,0 +1,15 @@ +- // MIR for `cannot_opt_generic` before RemoveUnneededDrops ++ // MIR for `cannot_opt_generic` after RemoveUnneededDrops + + fn cannot_opt_generic(_1: T) -> () { + let mut _0: (); + + bb0: { + drop(_1) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } + } + diff --git a/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-abort.diff b/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-abort.diff deleted file mode 100644 index 0c73602bec84b..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-abort.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `cannot_opt_generic` before RemoveUnneededDrops -+ // MIR for `cannot_opt_generic` after RemoveUnneededDrops - - fn cannot_opt_generic(_1: T) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: T; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { - nop; - StorageLive(_3); - _3 = move _1; - drop(_3) -> [return: bb1, unwind unreachable]; - } - - bb1: { - StorageDead(_3); - nop; - nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-unwind.diff b/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-unwind.diff deleted file mode 100644 index 59cce9fbcdd0a..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.panic-unwind.diff +++ /dev/null @@ -1,30 +0,0 @@ -- // MIR for `cannot_opt_generic` before RemoveUnneededDrops -+ // MIR for `cannot_opt_generic` after RemoveUnneededDrops - - fn cannot_opt_generic(_1: T) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: T; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { - nop; - StorageLive(_3); - _3 = move _1; - drop(_3) -> [return: bb2, unwind: bb1]; - } - - bb1 (cleanup): { - resume; - } - - bb2: { - StorageDead(_3); - nop; - nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff b/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff new file mode 100644 index 0000000000000..3d67cada5ddfd --- /dev/null +++ b/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff @@ -0,0 +1,15 @@ +- // MIR for `dont_opt` before RemoveUnneededDrops ++ // MIR for `dont_opt` after RemoveUnneededDrops + + fn dont_opt(_1: Vec) -> () { + let mut _0: (); + + bb0: { + drop(_1) -> [return: bb1, unwind unreachable]; + } + + bb1: { + return; + } + } + diff --git a/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-abort.diff b/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-abort.diff deleted file mode 100644 index 428b366b5a684..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-abort.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `dont_opt` before RemoveUnneededDrops -+ // MIR for `dont_opt` after RemoveUnneededDrops - - fn dont_opt(_1: Vec) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: std::vec::Vec; - scope 1 (inlined std::mem::drop::>) { - } - - bb0: { - nop; - StorageLive(_3); - _3 = move _1; - drop(_3) -> [return: bb1, unwind unreachable]; - } - - bb1: { - StorageDead(_3); - nop; - nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-unwind.diff b/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-unwind.diff deleted file mode 100644 index 445c1f82a96f8..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.dont_opt.RemoveUnneededDrops.panic-unwind.diff +++ /dev/null @@ -1,30 +0,0 @@ -- // MIR for `dont_opt` before RemoveUnneededDrops -+ // MIR for `dont_opt` after RemoveUnneededDrops - - fn dont_opt(_1: Vec) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: std::vec::Vec; - scope 1 (inlined std::mem::drop::>) { - } - - bb0: { - nop; - StorageLive(_3); - _3 = move _1; - drop(_3) -> [return: bb2, unwind: bb1]; - } - - bb1 (cleanup): { - resume; - } - - bb2: { - StorageDead(_3); - nop; - nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.diff b/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.diff new file mode 100644 index 0000000000000..cb7e58ca1a1f9 --- /dev/null +++ b/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.diff @@ -0,0 +1,15 @@ +- // MIR for `opt` before RemoveUnneededDrops ++ // MIR for `opt` after RemoveUnneededDrops + + fn opt(_1: bool) -> () { + let mut _0: (); + + bb0: { +- drop(_1) -> [return: bb1, unwind unreachable]; +- } +- +- bb1: { + return; + } + } + diff --git a/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-abort.diff b/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-abort.diff deleted file mode 100644 index 01eb6d4901f76..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-abort.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `opt` before RemoveUnneededDrops -+ // MIR for `opt` after RemoveUnneededDrops - - fn opt(_1: bool) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: bool; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { -- nop; - StorageLive(_3); - _3 = copy _1; -- drop(_3) -> [return: bb1, unwind unreachable]; -- } -- -- bb1: { - StorageDead(_3); -- nop; -- nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-unwind.diff b/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-unwind.diff deleted file mode 100644 index c2c3cb76e8328..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.opt.RemoveUnneededDrops.panic-unwind.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `opt` before RemoveUnneededDrops -+ // MIR for `opt` after RemoveUnneededDrops - - fn opt(_1: bool) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: bool; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { -- nop; - StorageLive(_3); - _3 = copy _1; -- drop(_3) -> [return: bb1, unwind continue]; -- } -- -- bb1: { - StorageDead(_3); -- nop; -- nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff b/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff new file mode 100644 index 0000000000000..1e166eee9fb07 --- /dev/null +++ b/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff @@ -0,0 +1,15 @@ +- // MIR for `opt_generic_copy` before RemoveUnneededDrops ++ // MIR for `opt_generic_copy` after RemoveUnneededDrops + + fn opt_generic_copy(_1: T) -> () { + let mut _0: (); + + bb0: { +- drop(_1) -> [return: bb1, unwind unreachable]; +- } +- +- bb1: { + return; + } + } + diff --git a/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-abort.diff b/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-abort.diff deleted file mode 100644 index a82ede6196ed3..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-abort.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `opt_generic_copy` before RemoveUnneededDrops -+ // MIR for `opt_generic_copy` after RemoveUnneededDrops - - fn opt_generic_copy(_1: T) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: T; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { -- nop; - StorageLive(_3); - _3 = copy _1; -- drop(_3) -> [return: bb1, unwind unreachable]; -- } -- -- bb1: { - StorageDead(_3); -- nop; -- nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-unwind.diff b/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-unwind.diff deleted file mode 100644 index 6e7c9ead740f0..0000000000000 --- a/tests/mir-opt/remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.panic-unwind.diff +++ /dev/null @@ -1,26 +0,0 @@ -- // MIR for `opt_generic_copy` before RemoveUnneededDrops -+ // MIR for `opt_generic_copy` after RemoveUnneededDrops - - fn opt_generic_copy(_1: T) -> () { - debug x => _1; - let mut _0: (); - let _2: (); - let mut _3: T; - scope 1 (inlined std::mem::drop::) { - } - - bb0: { -- nop; - StorageLive(_3); - _3 = copy _1; -- drop(_3) -> [return: bb1, unwind continue]; -- } -- -- bb1: { - StorageDead(_3); -- nop; -- nop; - return; - } - } - diff --git a/tests/mir-opt/remove_unneeded_drops.rs b/tests/mir-opt/remove_unneeded_drops.rs index cad79e0aa0cbc..49dc611838e38 100644 --- a/tests/mir-opt/remove_unneeded_drops.rs +++ b/tests/mir-opt/remove_unneeded_drops.rs @@ -1,28 +1,56 @@ -// skip-filecheck -// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +//@ test-mir-pass: RemoveUnneededDrops + +#![feature(custom_mir, core_intrinsics)] +use std::intrinsics::mir::*; + // EMIT_MIR remove_unneeded_drops.opt.RemoveUnneededDrops.diff +#[custom_mir(dialect = "runtime")] fn opt(x: bool) { - drop(x); + // CHECK-LABEL: fn opt( + // CHECK-NOT: drop( + mir! { + { Drop(x, ReturnTo(bb1), UnwindUnreachable()) } + bb1 = { Return() } + } } // EMIT_MIR remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff +#[custom_mir(dialect = "runtime")] fn dont_opt(x: Vec) { - drop(x); + // CHECK-LABEL: fn dont_opt( + // CHECK: drop( + mir! { + { Drop(x, ReturnTo(bb1), UnwindUnreachable()) } + bb1 = { Return() } + } } // EMIT_MIR remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff +#[custom_mir(dialect = "runtime")] fn opt_generic_copy(x: T) { - drop(x); + // CHECK-LABEL: fn opt_generic_copy( + // CHECK-NOT: drop( + mir! { + { Drop(x, ReturnTo(bb1), UnwindUnreachable()) } + bb1 = { Return() } + } } // EMIT_MIR remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff // since the pass is not running on monomorphisized code, // we can't (but probably should) optimize this +#[custom_mir(dialect = "runtime")] fn cannot_opt_generic(x: T) { - drop(x); + // CHECK-LABEL: fn cannot_opt_generic( + // CHECK: drop( + mir! { + { Drop(x, ReturnTo(bb1), UnwindUnreachable()) } + bb1 = { Return() } + } } fn main() { + // CHECK-LABEL: fn main( opt(true); opt_generic_copy(42); cannot_opt_generic(42); From ee0118f8a1dbfcad06b8c09644553a3fc3e070d4 Mon Sep 17 00:00:00 2001 From: David Tenty Date: Wed, 30 Jul 2025 16:45:17 -0400 Subject: [PATCH 14/24] [test][AIX] ignore extern_weak linkage test The AIX linkage model doesn't support ELF style extern_weak semantic, so just skip this test, like other platforms that don't have it. --- tests/ui/linkage-attr/propagate-generic-issue-18804/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/linkage-attr/propagate-generic-issue-18804/main.rs b/tests/ui/linkage-attr/propagate-generic-issue-18804/main.rs index 8c194ec50df4b..ad7b067447811 100644 --- a/tests/ui/linkage-attr/propagate-generic-issue-18804/main.rs +++ b/tests/ui/linkage-attr/propagate-generic-issue-18804/main.rs @@ -4,6 +4,7 @@ //@ ignore-emscripten no weak symbol support //@ ignore-apple no extern_weak linkage +//@ ignore-aix no extern_weak linkage //@ aux-build:lib.rs From 38d5f432585d6938bbb87c83534bbe8a73ce5e0f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 13 Jul 2025 20:38:12 +0200 Subject: [PATCH 15/24] Correctly handle `--no-run` rustdoc test option --- src/librustdoc/doctest.rs | 2 +- src/librustdoc/doctest/runner.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 0bef091468f2b..7216167425ab2 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -358,7 +358,7 @@ pub(crate) fn run_tests( ); for (doctest, scraped_test) in &doctests { - tests_runner.add_test(doctest, scraped_test, &target_str); + tests_runner.add_test(doctest, scraped_test, &target_str, rustdoc_options); } let (duration, ret) = tests_runner.run_merged_tests( rustdoc_test_options, diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index fcfa424968e48..5493d56456872 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -39,6 +39,7 @@ impl DocTestRunner { doctest: &DocTestBuilder, scraped_test: &ScrapedDocTest, target_str: &str, + opts: &RustdocOptions, ) { let ignore = match scraped_test.langstr.ignore { Ignore::All => true, @@ -62,6 +63,7 @@ impl DocTestRunner { self.nb_tests, &mut self.output, &mut self.output_merged_tests, + opts, ), )); self.supports_color &= doctest.supports_color; @@ -223,6 +225,7 @@ fn generate_mergeable_doctest( id: usize, output: &mut String, output_merged_tests: &mut String, + opts: &RustdocOptions, ) -> String { let test_id = format!("__doctest_{id}"); @@ -256,7 +259,7 @@ fn main() {returns_result} {{ ) .unwrap(); } - let not_running = ignore || scraped_test.langstr.no_run; + let not_running = ignore || scraped_test.no_run(opts); writeln!( output_merged_tests, " @@ -270,7 +273,7 @@ test::StaticTestFn( test_name = scraped_test.name, file = scraped_test.path(), line = scraped_test.line, - no_run = scraped_test.langstr.no_run, + no_run = scraped_test.no_run(opts), should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic, // Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply // don't give it the function to run. From 234b8acfc0999583e9a51e495e06dd267bb039af Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 7 Jul 2025 11:53:18 +0200 Subject: [PATCH 16/24] Correctly handle `should_panic` doctest attribute --- src/librustdoc/doctest.rs | 4 ++-- src/librustdoc/doctest/runner.rs | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 7216167425ab2..8e841671e9209 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -827,7 +827,7 @@ fn run_test( match result { Err(e) => return (duration, Err(TestFailure::ExecutionError(e))), Ok(out) => { - if langstr.should_panic && out.status.success() { + if langstr.should_panic && out.status.code() != Some(101) { return (duration, Err(TestFailure::UnexpectedRunPass)); } else if !langstr.should_panic && !out.status.success() { return (duration, Err(TestFailure::ExecutionFailure(out))); @@ -1137,7 +1137,7 @@ fn doctest_run_fn( eprint!("Test compiled successfully, but it's marked `compile_fail`."); } TestFailure::UnexpectedRunPass => { - eprint!("Test executable succeeded, but it's marked `should_panic`."); + eprint!("Test didn't panic, but it's marked `should_panic`."); } TestFailure::MissingErrorCodes(codes) => { eprint!("Some expected error codes were not found: {codes:?}"); diff --git a/src/librustdoc/doctest/runner.rs b/src/librustdoc/doctest/runner.rs index 5493d56456872..d02b32faa319e 100644 --- a/src/librustdoc/doctest/runner.rs +++ b/src/librustdoc/doctest/runner.rs @@ -136,13 +136,20 @@ mod __doctest_mod {{ }} #[allow(unused)] - pub fn doctest_runner(bin: &std::path::Path, test_nb: usize) -> ExitCode {{ + pub fn doctest_runner(bin: &std::path::Path, test_nb: usize, should_panic: bool) -> ExitCode {{ let out = std::process::Command::new(bin) .env(self::RUN_OPTION, test_nb.to_string()) .args(std::env::args().skip(1).collect::>()) .output() .expect(\"failed to run command\"); - if !out.status.success() {{ + if should_panic {{ + if out.status.code() != Some(101) {{ + eprintln!(\"Test didn't panic, but it's marked `should_panic`.\"); + ExitCode::FAILURE + }} else {{ + ExitCode::SUCCESS + }} + }} else if !out.status.success() {{ if let Some(code) = out.status.code() {{ eprintln!(\"Test executable failed (exit status: {{code}}).\"); }} else {{ @@ -265,7 +272,7 @@ fn main() {returns_result} {{ " mod {test_id} {{ pub const TEST: test::TestDescAndFn = test::TestDescAndFn::new_doctest( -{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, {should_panic}, +{test_name:?}, {ignore}, {file:?}, {line}, {no_run}, false, test::StaticTestFn( || {{{runner}}}, )); @@ -274,7 +281,6 @@ test::StaticTestFn( file = scraped_test.path(), line = scraped_test.line, no_run = scraped_test.no_run(opts), - should_panic = !scraped_test.langstr.no_run && scraped_test.langstr.should_panic, // Setting `no_run` to `true` in `TestDesc` still makes the test run, so we simply // don't give it the function to run. runner = if not_running { @@ -283,11 +289,12 @@ test::StaticTestFn( format!( " if let Some(bin_path) = crate::__doctest_mod::doctest_path() {{ - test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id})) + test::assert_test_result(crate::__doctest_mod::doctest_runner(bin_path, {id}, {should_panic})) }} else {{ test::assert_test_result(doctest_bundle::{test_id}::__main_fn()) }} ", + should_panic = scraped_test.langstr.should_panic, ) }, ) From 631ade8fbec16bac33fe1cba4a494f3052010c4f Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 4 Jul 2025 21:42:19 +0200 Subject: [PATCH 17/24] Add regression test for #143009 --- tests/run-make/rustdoc-should-panic/rmake.rs | 36 ++++++++++++++++++++ tests/run-make/rustdoc-should-panic/test.rs | 14 ++++++++ 2 files changed, 50 insertions(+) create mode 100644 tests/run-make/rustdoc-should-panic/rmake.rs create mode 100644 tests/run-make/rustdoc-should-panic/test.rs diff --git a/tests/run-make/rustdoc-should-panic/rmake.rs b/tests/run-make/rustdoc-should-panic/rmake.rs new file mode 100644 index 0000000000000..100a62f5db313 --- /dev/null +++ b/tests/run-make/rustdoc-should-panic/rmake.rs @@ -0,0 +1,36 @@ +// Ensure that `should_panic` doctests only succeed if the test actually panicked. +// Regression test for . + +//@ needs-target-std + +use run_make_support::rustdoc; + +fn check_output(output: String, edition: &str) { + let should_contain = &[ + "test test.rs - bad_exit_code (line 1) ... FAILED", + "test test.rs - did_not_panic (line 6) ... FAILED", + "test test.rs - did_panic (line 11) ... ok", + "---- test.rs - bad_exit_code (line 1) stdout ---- +Test executable failed (exit status: 1).", + "---- test.rs - did_not_panic (line 6) stdout ---- +Test didn't panic, but it's marked `should_panic`.", + "test result: FAILED. 1 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out;", + ]; + for text in should_contain { + assert!( + output.contains(text), + "output doesn't contains (edition: {edition}) {:?}\nfull output: {output}", + text + ); + } +} + +fn main() { + check_output(rustdoc().input("test.rs").arg("--test").run_fail().stdout_utf8(), "2015"); + + // Same check with the merged doctest feature (enabled with the 2024 edition). + check_output( + rustdoc().input("test.rs").arg("--test").edition("2024").run_fail().stdout_utf8(), + "2024", + ); +} diff --git a/tests/run-make/rustdoc-should-panic/test.rs b/tests/run-make/rustdoc-should-panic/test.rs new file mode 100644 index 0000000000000..1eea8e1e1958c --- /dev/null +++ b/tests/run-make/rustdoc-should-panic/test.rs @@ -0,0 +1,14 @@ +/// ``` +/// std::process::exit(1); +/// ``` +fn bad_exit_code() {} + +/// ```should_panic +/// std::process::exit(1); +/// ``` +fn did_not_panic() {} + +/// ```should_panic +/// panic!("yeay"); +/// ``` +fn did_panic() {} From 3f7b9892aef175b88d08ed6c2fb05a2e66ee7d9c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 7 Jul 2025 13:35:02 +0200 Subject: [PATCH 18/24] Update std doctests --- library/std/src/error.rs | 16 ++++++++++++---- .../failed-doctest-should-panic-2021.stdout | 2 +- .../doctest/failed-doctest-should-panic.stdout | 5 +++-- tests/rustdoc-ui/doctest/wrong-ast-2024.stdout | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/library/std/src/error.rs b/library/std/src/error.rs index def5f984c88e4..09bfc83ebca6c 100644 --- a/library/std/src/error.rs +++ b/library/std/src/error.rs @@ -123,7 +123,7 @@ use crate::fmt::{self, Write}; /// the `Debug` output means `Report` is an ideal starting place for formatting errors returned /// from `main`. /// -/// ```should_panic +/// ``` /// #![feature(error_reporter)] /// use std::error::Report; /// # use std::error::Error; @@ -154,10 +154,14 @@ use crate::fmt::{self, Write}; /// # Err(SuperError { source: SuperErrorSideKick }) /// # } /// -/// fn main() -> Result<(), Report> { +/// fn run() -> Result<(), Report> { /// get_super_error()?; /// Ok(()) /// } +/// +/// fn main() { +/// assert!(run().is_err()); +/// } /// ``` /// /// This example produces the following output: @@ -170,7 +174,7 @@ use crate::fmt::{self, Write}; /// output format. If you want to make sure your `Report`s are pretty printed and include backtrace /// you will need to manually convert and enable those flags. /// -/// ```should_panic +/// ``` /// #![feature(error_reporter)] /// use std::error::Report; /// # use std::error::Error; @@ -201,12 +205,16 @@ use crate::fmt::{self, Write}; /// # Err(SuperError { source: SuperErrorSideKick }) /// # } /// -/// fn main() -> Result<(), Report> { +/// fn run() -> Result<(), Report> { /// get_super_error() /// .map_err(Report::from) /// .map_err(|r| r.pretty(true).show_backtrace(true))?; /// Ok(()) /// } +/// +/// fn main() { +/// assert!(run().is_err()); +/// } /// ``` /// /// This example produces the following output: diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout b/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout index 9f4d60e6f4de5..f8413756e3d6d 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic-2021.stdout @@ -5,7 +5,7 @@ test $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) ... FAILED failures: ---- $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) stdout ---- -Test executable succeeded, but it's marked `should_panic`. +Test didn't panic, but it's marked `should_panic`. failures: $DIR/failed-doctest-should-panic-2021.rs - Foo (line 10) diff --git a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout index 9047fe0dcdd93..61099e6424ae7 100644 --- a/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout +++ b/tests/rustdoc-ui/doctest/failed-doctest-should-panic.stdout @@ -1,11 +1,12 @@ running 1 test -test $DIR/failed-doctest-should-panic.rs - Foo (line 12) - should panic ... FAILED +test $DIR/failed-doctest-should-panic.rs - Foo (line 12) ... FAILED failures: ---- $DIR/failed-doctest-should-panic.rs - Foo (line 12) stdout ---- -note: test did not panic as expected at $DIR/failed-doctest-should-panic.rs:12:0 +Test didn't panic, but it's marked `should_panic`. + failures: $DIR/failed-doctest-should-panic.rs - Foo (line 12) diff --git a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout index 13567b41e51f5..27f9a0157a6cc 100644 --- a/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout +++ b/tests/rustdoc-ui/doctest/wrong-ast-2024.stdout @@ -1,6 +1,6 @@ running 1 test -test $DIR/wrong-ast-2024.rs - three (line 20) - should panic ... ok +test $DIR/wrong-ast-2024.rs - three (line 20) ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME From c365e6ee19ae9784a5e1ce2746c30c1c2f96a8a4 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 13 Jul 2025 20:49:29 +0200 Subject: [PATCH 19/24] Add regression test for #143858 --- .../doctest/no-run.edition2021.stdout | 12 +++++ .../doctest/no-run.edition2024.stdout | 18 ++++++++ tests/rustdoc-ui/doctest/no-run.rs | 44 +++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 tests/rustdoc-ui/doctest/no-run.edition2021.stdout create mode 100644 tests/rustdoc-ui/doctest/no-run.edition2024.stdout create mode 100644 tests/rustdoc-ui/doctest/no-run.rs diff --git a/tests/rustdoc-ui/doctest/no-run.edition2021.stdout b/tests/rustdoc-ui/doctest/no-run.edition2021.stdout new file mode 100644 index 0000000000000..937cd76bfb462 --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.edition2021.stdout @@ -0,0 +1,12 @@ + +running 7 tests +test $DIR/no-run.rs - f (line 14) - compile ... ok +test $DIR/no-run.rs - f (line 17) - compile ... ok +test $DIR/no-run.rs - f (line 20) ... ignored +test $DIR/no-run.rs - f (line 23) - compile ... ok +test $DIR/no-run.rs - f (line 29) - compile fail ... ok +test $DIR/no-run.rs - f (line 34) - compile ... ok +test $DIR/no-run.rs - f (line 38) - compile ... ok + +test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME + diff --git a/tests/rustdoc-ui/doctest/no-run.edition2024.stdout b/tests/rustdoc-ui/doctest/no-run.edition2024.stdout new file mode 100644 index 0000000000000..921e059979b1f --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.edition2024.stdout @@ -0,0 +1,18 @@ + +running 5 tests +test $DIR/no-run.rs - f (line 14) - compile ... ok +test $DIR/no-run.rs - f (line 17) - compile ... ok +test $DIR/no-run.rs - f (line 23) - compile ... ok +test $DIR/no-run.rs - f (line 34) - compile ... ok +test $DIR/no-run.rs - f (line 38) - compile ... ok + +test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + + +running 2 tests +test $DIR/no-run.rs - f (line 20) ... ignored +test $DIR/no-run.rs - f (line 29) - compile fail ... ok + +test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in $TIME + +all doctests ran in $TIME; merged doctests compilation took $TIME diff --git a/tests/rustdoc-ui/doctest/no-run.rs b/tests/rustdoc-ui/doctest/no-run.rs new file mode 100644 index 0000000000000..78198badd43b5 --- /dev/null +++ b/tests/rustdoc-ui/doctest/no-run.rs @@ -0,0 +1,44 @@ +// This test ensures that the `--no-run` flag works the same between normal and merged doctests. +// Regression test for . + +//@ check-pass +//@ revisions: edition2021 edition2024 +//@ [edition2021]edition:2021 +//@ [edition2024]edition:2024 +//@ compile-flags:-Z unstable-options --test --no-run --test-args=--test-threads=1 +//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" +//@ normalize-stdout: "ran in \d+\.\d+s" -> "ran in $$TIME" +//@ normalize-stdout: "compilation took \d+\.\d+s" -> "compilation took $$TIME" + +/// ``` +/// let a = true; +/// ``` +/// ```should_panic +/// panic!() +/// ``` +/// ```ignore (incomplete-code) +/// fn foo() { +/// ``` +/// ```no_run +/// loop { +/// println!("Hello, world"); +/// } +/// ``` +/// fails to compile +/// ```compile_fail +/// let x = 5; +/// x += 2; // shouldn't compile! +/// ``` +/// Ok the test does not run +/// ``` +/// panic!() +/// ``` +/// Ok the test does not run +/// ```should_panic +/// loop { +/// println!("Hello, world"); +/// panic!() +/// } +/// ``` +pub fn f() {} From 5aec4379e304ef280ee9a470e7ab121e4a81fd17 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Thu, 31 Jul 2025 23:59:06 +0200 Subject: [PATCH 20/24] detect infinite recursion with tail calls in ctfe --- compiler/rustc_mir_transform/src/ctfe_limit.rs | 2 +- .../infinite-recursion-in-ctfe.rs | 10 ++++++++++ .../infinite-recursion-in-ctfe.stderr | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.rs create mode 100644 tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.stderr diff --git a/compiler/rustc_mir_transform/src/ctfe_limit.rs b/compiler/rustc_mir_transform/src/ctfe_limit.rs index fb17cca30f4a5..ac46336b83473 100644 --- a/compiler/rustc_mir_transform/src/ctfe_limit.rs +++ b/compiler/rustc_mir_transform/src/ctfe_limit.rs @@ -18,7 +18,7 @@ impl<'tcx> crate::MirPass<'tcx> for CtfeLimit { .basic_blocks .iter_enumerated() .filter_map(|(node, node_data)| { - if matches!(node_data.terminator().kind, TerminatorKind::Call { .. }) + if matches!(node_data.terminator().kind, TerminatorKind::Call { .. } | TerminatorKind::TailCall { .. }) // Back edges in a CFG indicate loops || has_back_edge(doms, node, node_data) { diff --git a/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.rs b/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.rs new file mode 100644 index 0000000000000..0c55f13c16c22 --- /dev/null +++ b/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.rs @@ -0,0 +1,10 @@ +#![feature(explicit_tail_calls)] +#![expect(incomplete_features)] + +const _: () = f(); + +const fn f() { + become f(); //~ error: constant evaluation is taking a long time +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.stderr b/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.stderr new file mode 100644 index 0000000000000..b5a96114a5837 --- /dev/null +++ b/tests/ui/explicit-tail-calls/infinite-recursion-in-ctfe.stderr @@ -0,0 +1,17 @@ +error: constant evaluation is taking a long time + --> $DIR/infinite-recursion-in-ctfe.rs:7:5 + | +LL | become f(); + | ^^^^^^^^^^ + | + = note: this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval. + If your compilation actually takes a long time, you can safely allow the lint. +help: the constant being evaluated + --> $DIR/infinite-recursion-in-ctfe.rs:4:1 + | +LL | const _: () = f(); + | ^^^^^^^^^^^ + = note: `#[deny(long_running_const_eval)]` on by default + +error: aborting due to 1 previous error + From 040f71e8123b5994177f787e64aecd9b06cdfa7e Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 25 Jul 2025 15:18:51 +0200 Subject: [PATCH 21/24] loop match: error on `#[const_continue]` outside `#[loop_match]` --- compiler/rustc_hir/src/hir.rs | 2 +- compiler/rustc_hir_typeck/src/loops.rs | 50 +++++++++++-------- compiler/rustc_mir_build/messages.ftl | 2 +- compiler/rustc_mir_build/src/errors.rs | 4 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 4 +- .../ui/loop-match/const-continue-to-block.rs | 21 ++++++++ .../loop-match/const-continue-to-block.stderr | 17 ++++++- tests/ui/loop-match/invalid.rs | 19 +++++++ tests/ui/loop-match/invalid.stderr | 22 ++++++-- 9 files changed, 107 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 1b1b3ced44db7..08361718108bf 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -3016,7 +3016,7 @@ impl fmt::Display for LoopIdError { } } -#[derive(Copy, Clone, Debug, HashStable_Generic)] +#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)] pub struct Destination { /// This is `Some(_)` iff there is an explicit user-specified 'label pub label: Option