diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 39e886227d946..6610a305748c2 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -237,6 +237,8 @@ declare_features! ( (internal, field_representing_type_raw, "CURRENT_RUSTC_VERSION", None), /// Outputs useful `assert!` messages (unstable, generic_assert, "1.63.0", None), + /// Allows using `arg@` rustdoc disambiguator + (unstable, intra_doc_arg, "CURRENT_RUSTC_VERSION", None), /// Allows using the #[rustc_intrinsic] attribute. (internal, intrinsics, "1.0.0", None), /// Allows using `#[lang = ".."]` attribute for linking items to special compiler logic. diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 257ac3f51c2c1..0aa0a9a9f13e1 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1095,6 +1095,7 @@ symbols! { into_future, into_iter, into_try_type, + intra_doc_arg, intra_doc_pointers, intrinsics, irrefutable_let_patterns, diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index c595a0ae2d485..23f69395e64dd 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -576,7 +576,9 @@ impl Item { .iter() .filter_map(|ItemLink { link: s, link_text, page_id: id, fragment }| { debug!(?id); - if let Ok(HrefInfo { mut url, .. }) = href(*id, cx) { + if let Some(id) = id + && let Ok(HrefInfo { mut url, .. }) = href(*id, cx) + { debug!(?url); match fragment { Some(UrlFragment::Item(def_id)) => { @@ -593,7 +595,14 @@ impl Item { original_text: s.clone(), new_text: link_text.clone(), tooltip: link_tooltip(*id, fragment, cx).to_string(), - href: url, + href: Some(url), + }) + } else if id.is_none() { + Some(RenderedLink { + original_text: s.clone(), + new_text: link_text.clone(), + tooltip: format!("function argument {link_text}"), + href: None, }) } else { None @@ -616,7 +625,7 @@ impl Item { .map(|ItemLink { link: s, link_text, .. }| RenderedLink { original_text: s.clone(), new_text: link_text.clone(), - href: String::new(), + href: Some(String::new()), tooltip: String::new(), }) .collect() @@ -990,7 +999,10 @@ pub(crate) struct ItemLink { /// The `DefId` of the Item whose **HTML Page** contains the item being /// linked to. This will be different to `item_id` on item's that don't /// have their own page, such as struct fields and enum variants. - pub(crate) page_id: DefId, + /// + /// If `None`, no link will be generated, but link syntax will be + /// removed. For example, `[arg@n]` will become just `n`. + pub(crate) page_id: Option, /// The url fragment to append to the link pub(crate) fragment: Option, } @@ -1003,7 +1015,9 @@ pub struct RenderedLink { /// The text to display in the HTML pub(crate) new_text: Box, /// The URL to put in the `href` - pub(crate) href: String, + /// + /// If this is `None`, no link will be generated at all. + pub(crate) href: Option, /// The tooltip. pub(crate) tooltip: String, } diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 858545bd09847..6b32423c1f8da 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -362,6 +362,8 @@ impl<'a, I: Iterator>> Iterator for CodeBlocks<'_, 'a, I> { struct LinkReplacerInner<'a> { links: &'a [RenderedLink], shortcut_link: Option<&'a RenderedLink>, + /// If `true`, the next [`TagEnd::Link`] will be removed + remove_closing_link_tag: bool, } struct LinkReplacer<'a, I: Iterator>> { @@ -371,7 +373,12 @@ struct LinkReplacer<'a, I: Iterator>> { impl<'a, I: Iterator>> LinkReplacer<'a, I> { fn new(iter: I, links: &'a [RenderedLink]) -> Self { - LinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } } + LinkReplacer { + iter, + inner: { + LinkReplacerInner { links, shortcut_link: None, remove_closing_link_tag: false } + }, + } } } @@ -384,7 +391,12 @@ struct SpannedLinkReplacer<'a, I: Iterator>> { impl<'a, I: Iterator>> SpannedLinkReplacer<'a, I> { fn new(iter: I, links: &'a [RenderedLink]) -> Self { - SpannedLinkReplacer { iter, inner: { LinkReplacerInner { links, shortcut_link: None } } } + SpannedLinkReplacer { + iter, + inner: { + LinkReplacerInner { links, shortcut_link: None, remove_closing_link_tag: false } + }, + } } } @@ -401,10 +413,22 @@ impl<'a> LinkReplacerInner<'a> { title, .. }) => { + // an empty destination URL ("") will be generated when + // rustdoc encounters argument disambiguation syntax: [`arg@foo`], which + // currently won't generate a link - so, the link syntax is simply stripped + if dest_url.is_empty() { + *event = Event::Text("".into()); + self.remove_closing_link_tag = true; + return; + } + debug!("saw start of shortcut link to {dest_url} with title {title}"); // If this is a shortcut link, it was resolved by the broken_link_callback. // So the URL will already be updated properly. - let link = self.links.iter().find(|&link| *link.href == **dest_url); + let link = self + .links + .iter() + .find(|&link| link.href.as_ref().is_none_or(|href| *href == **dest_url)); // Since this is an external iterator, we can't replace the inner text just yet. // Store that we saw a link so we know to replace it later. if let Some(link) = link { @@ -416,11 +440,49 @@ impl<'a> LinkReplacerInner<'a> { } } } + // Remove the end of link + Event::End(TagEnd::Link) if self.remove_closing_link_tag => { + *event = Event::Text("".into()); + self.remove_closing_link_tag = false; + } // Now that we're done with the shortcut link, don't replace any more text. Event::End(TagEnd::Link) if self.shortcut_link.is_some() => { debug!("saw end of shortcut link"); self.shortcut_link = None; } + // We are currently inside of the link: + // + // [`arg@f`] + // + // And converting that into just: + // + // `f` + Event::Code(text) if self.remove_closing_link_tag => { + if let Some(link) = self.links.iter().find(|link| { + let original_text: &str = &*link.original_text; + let text: &str = text; + + // compare contents of inline code block with contents of link. because + // the original link had an inline code block inside, we remove that from the comparison + // + // original link: + // [`fn@f`] + // ^^^^^^ original_text + // + // the Event::Code(text) we received: + // `fn@f` + // ^^^^ text + // + // to compare the 2 for equality, backticks have to be stripped. + let Some(original_text) = original_text.strip_circumfix("`", "`") else { + return false; + }; + + original_text == text + }) { + *text = CowStr::Borrowed(&link.new_text); + } + } // Handle backticks in inline code blocks, but only if we're in the middle of a shortcut link. // [`fn@f`] Event::Code(text) => { @@ -442,6 +504,23 @@ impl<'a> LinkReplacerInner<'a> { debug!("replacing {text} with {new_text}", new_text = link.new_text); *text = CowStr::Borrowed(&link.new_text); } + }; + } + // We are currently inside of the link: + // + // [arg@f] + // + // And converting that into just: + // + // f + Event::Text(text) if self.remove_closing_link_tag => { + if let Some(link) = self.links.iter().find(|link| { + let original_text: &str = &*link.original_text; + let text: &str = text; + + original_text == text + }) { + *text = CowStr::Borrowed(&link.new_text); } } // Replace plain text in links, but only in the middle of a shortcut link. @@ -466,7 +545,13 @@ impl<'a> LinkReplacerInner<'a> { if let Some(link) = self.links.iter().find(|&link| *link.original_text == **dest_url) { - *dest_url = CowStr::Borrowed(link.href.as_ref()); + let Some(href) = link.href.as_ref() else { + // Remove link. + *event = Event::Text("".into()); + self.remove_closing_link_tag = true; + return; + }; + *dest_url = CowStr::Borrowed(href); if title.is_empty() && !link.tooltip.is_empty() { *title = CowStr::Borrowed(link.tooltip.as_ref()); } @@ -1350,10 +1435,9 @@ impl<'a> Markdown<'a> { } = self; let replacer = move |broken_link: BrokenLink<'_>| { - links - .iter() - .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| { + (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()) + }) }; let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer)); @@ -1428,10 +1512,9 @@ impl MarkdownWithToc<'_> { return (Toc { entries: Vec::new() }, String::new()); } let mut replacer = |broken_link: BrokenLink<'_>| { - links - .iter() - .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| { + (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()) + }) }; let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(&mut replacer)); @@ -1472,10 +1555,9 @@ impl<'a> MarkdownItemInfo<'a> { } let replacer = move |broken_link: BrokenLink<'_>| { - links - .iter() - .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| { + (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()) + }) }; let p = Parser::new_with_broken_link_callback(md, main_body_opts(), Some(replacer)); @@ -1509,10 +1591,9 @@ impl MarkdownSummaryLine<'_> { } let mut replacer = |broken_link: BrokenLink<'_>| { - links - .iter() - .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + links.iter().find(|link| *link.original_text == *broken_link.reference).map(|link| { + (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into()) + }) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)) @@ -1559,7 +1640,7 @@ fn markdown_summary_with_limit( link_names .iter() .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + .map(|link| (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); @@ -1640,7 +1721,7 @@ pub(crate) fn plain_text_summary(md: &str, link_names: &[RenderedLink]) -> Strin link_names .iter() .find(|link| *link.original_text == *broken_link.reference) - .map(|link| (link.href.as_str().into(), link.tooltip.as_str().into())) + .map(|link| (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())) }; let p = Parser::new_with_broken_link_callback(md, summary_opts(), Some(&mut replacer)); diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index bc9ad1606b8a4..c4fbca502b94b 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -31,14 +31,14 @@ impl JsonRenderer<'_> { .get(&item.item_id) .into_iter() .flatten() - .map(|clean::ItemLink { link, page_id, fragment, .. }| { + .flat_map(|clean::ItemLink { link, page_id, fragment, .. }| { let id = match fragment { - Some(UrlFragment::Item(frag_id)) => *frag_id, + Some(UrlFragment::Item(frag_id)) => Some(*frag_id), // FIXME: Pass the `UserWritten` segment to JSON consumer. - Some(UrlFragment::UserWritten(_)) | None => *page_id, + Some(UrlFragment::UserWritten(_)) | None => page_id.as_ref().copied(), }; - (String::from(&**link), self.id_from_item_default(id.into())) + id.map(|id| (String::from(&**link), self.id_from_item_default(id.into()))) }) .collect(); let docs = item.opt_doc_value(); diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 55075001e0fef..5d32d398bdcc1 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -11,6 +11,7 @@ #![feature(iter_intersperse)] #![feature(iter_order_by)] #![feature(rustc_private)] +#![feature(strip_circumfix)] #![feature(test)] #![feature(trim_prefix_suffix)] #![recursion_limit = "256"] diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 3e68ed950ce16..ef1688c5d8356 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1168,6 +1168,52 @@ impl LinkCollector<'_, '_> { pp_link.as_ref().map_err(|err| err.report(self.cx, diag_info.clone())).ok()?; let disambiguator = *disambiguator; + // if the disambiguator is `arg@argument`, then the item must be a function that has the `argument`. + if disambiguator == Some(Disambiguator::Argument) { + if !self.cx.tcx.features().intra_doc_arg() { + self.report_intra_doc_arg_feature_gate(dox, &diag_info.link_range, item); + } + + match &item.inner.kind { + clean::types::ItemKind::MethodItem(f, _) + | clean::types::ItemKind::ForeignFunctionItem(f, _) + | clean::types::ItemKind::FunctionItem(f) + | clean::types::ItemKind::RequiredMethodItem(f, _) => { + let contains_arg = f.decl.inputs.iter().any(|input| { + input.name.is_some_and(|it| { + let a: &str = &*link_text; + it.as_str() == a + }) + }); + + if contains_arg { + return Some(ItemLink { + link: diag_info.ori_link.into(), + link_text: link_text.clone(), + page_id: None, + fragment: None, + }); + } else { + unknown_arg_error( + self.cx, + diag_info.clone(), + diag_info.link_range.clone(), + link_text, + ); + return None; + } + } + _ => { + invalid_arg_disambiguator( + self.cx, + diag_info.clone(), + diag_info.link_range.clone(), + ); + return None; + } + } + } + let mut resolved = self.resolve_with_disambiguator_cached( ResolutionInfo { item_id, @@ -1360,7 +1406,7 @@ impl LinkCollector<'_, '_> { res.def_id(self.cx.tcx).map(|page_id| ItemLink { link: Box::::from(diag_info.ori_link), link_text: link_text.clone(), - page_id, + page_id: Some(page_id), fragment, }) } @@ -1382,7 +1428,7 @@ impl LinkCollector<'_, '_> { Some(ItemLink { link: Box::::from(diag_info.ori_link), link_text: link_text.clone(), - page_id, + page_id: Some(page_id), fragment, }) } @@ -1416,6 +1462,8 @@ impl LinkCollector<'_, '_> { | (_, Some(Disambiguator::Namespace(_))) // If no disambiguator given, allow anything | (_, None) + // arg@argument disambiguator + | (_, Some(Disambiguator::Argument)) // All of these are valid, so do nothing => {} (actual, Some(Disambiguator::Kind(expected))) if actual == expected => {} @@ -1489,6 +1537,30 @@ impl LinkCollector<'_, '_> { .emit(); } + fn report_intra_doc_arg_feature_gate( + &self, + dox: &str, + ori_link: &MarkdownLinkRange, + item: &Item, + ) { + let span = match source_span_for_markdown_range( + self.cx.tcx, + dox, + ori_link.inner_range(), + &item.attrs.doc_strings, + ) { + Some((sp, _)) => sp, + None => item.attr_span(self.cx.tcx), + }; + rustc_session::parse::feature_err( + self.cx.tcx.sess, + sym::intra_doc_arg, + span, + "`arg@` disambiguators are experimental", + ) + .emit(); + } + fn resolve_with_disambiguator_cached( &mut self, key: ResolutionInfo, @@ -1702,6 +1774,10 @@ enum Disambiguator { Kind(DefKind), /// `type@` Namespace(Namespace), + /// References a function argument + /// + /// `arg@` or `argument@` + Argument, } impl Disambiguator { @@ -1744,6 +1820,7 @@ impl Disambiguator { "type" => NS(Namespace::TypeNS), "value" => NS(Namespace::ValueNS), "macro" => NS(Namespace::MacroNS), + "arg" | "argument" => Disambiguator::Argument, "prim" | "primitive" => Primitive, "tyalias" | "typealias" => Kind(DefKind::TyAlias), _ => return Err((format!("unknown disambiguator `{prefix}`"), 0..idx)), @@ -1782,6 +1859,7 @@ impl Disambiguator { Self::Namespace(n) => n, // for purposes of link resolution, fields are in the value namespace. Self::Kind(DefKind::Field) => ValueNS, + Self::Argument => ValueNS, Self::Kind(k) => { k.ns().expect("only DefKinds with a valid namespace can be disambiguators") } @@ -1793,7 +1871,7 @@ impl Disambiguator { match self { Self::Namespace(_) => panic!("article() doesn't make sense for namespaces"), Self::Kind(k) => k.article(), - Self::Primitive => "a", + Self::Argument | Self::Primitive => "a", } } @@ -1804,6 +1882,7 @@ impl Disambiguator { // printing "module" vs "crate" so using the wrong ID is not a huge problem Self::Kind(k) => k.descr(CRATE_DEF_ID.to_def_id()), Self::Primitive => "builtin type", + Self::Argument => "argument", } } } @@ -2276,6 +2355,39 @@ fn disambiguator_error( }); } +/// Unknown function parameter error when using `arg@name` disambiguator +fn unknown_arg_error( + cx: &DocContext<'_>, + mut diag_info: DiagnosticInfo<'_>, + range: MarkdownLinkRange, + arg: impl Display, +) { + diag_info.link_range = range; + report_diagnostic( + cx.tcx, + BROKEN_INTRA_DOC_LINKS, + format!("argument `{arg}` does not exist"), + &diag_info, + |_diag, _sp, _link_range| {}, + ); +} + +/// `arg@` disambiguator used on a non-function +fn invalid_arg_disambiguator( + cx: &DocContext<'_>, + mut diag_info: DiagnosticInfo<'_>, + range: MarkdownLinkRange, +) { + diag_info.link_range = range; + report_diagnostic( + cx.tcx, + BROKEN_INTRA_DOC_LINKS, + "`arg@` disambiguator can only be used in the documentation of functions", + &diag_info, + |_diag, _sp, _link_range| {}, + ); +} + fn report_malformed_generics( cx: &DocContext<'_>, diag_info: DiagnosticInfo<'_>, diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 86b8e7b6f86aa..c13706bc5dcb3 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -117,8 +117,9 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & let mut replacer = |broken_link: BrokenLink<'_>| { if let Some(link) = link_names.iter().find(|link| *link.original_text == *broken_link.reference) + && let Some(href) = link.href.as_deref() { - Some((link.href.as_str().into(), link.new_text.to_string().into())) + Some((href.into(), link.new_text.to_string().into())) } else if matches!(&broken_link.link_type, LinkType::Reference | LinkType::ReferenceUnknown) { // If the link is shaped [like][this], suppress any broken HTML in the [this] part. diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs index 4c33b82d14b44..4b371893cdcdb 100644 --- a/src/librustdoc/passes/lint/unescaped_backticks.rs +++ b/src/librustdoc/passes/lint/unescaped_backticks.rs @@ -20,7 +20,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item, hir_id: HirId, dox: & link_names .iter() .find(|link| *link.original_text == *broken_link.reference) - .map(|link| ((*link.href).into(), (*link.new_text).into())) + .map(|link| (link.href.as_deref().unwrap_or("").into(), link.tooltip.as_str().into())) }; let parser = Parser::new_with_broken_link_callback(dox, main_body_opts(), Some(&mut replacer)) .into_offset_iter(); diff --git a/tests/rustdoc-html/intra-doc-arg.rs b/tests/rustdoc-html/intra-doc-arg.rs new file mode 100644 index 0000000000000..f389f5f516c3d --- /dev/null +++ b/tests/rustdoc-html/intra-doc-arg.rs @@ -0,0 +1,35 @@ +#![crate_name = "foo"] +#![feature(intra_doc_arg)] + +/// **[arg@x1]** +//@ !has foo/index.html '//strong a' 'x1' +//@ has foo/index.html '//strong' 'x1' +pub fn a(x1: ()) {} + +pub struct X; + +impl X { + /// **[arg@x2]** + //@ !has foo/struct.X.html '//strong a' 'x2' + //@ has foo/struct.X.html '//strong' 'x2' + pub fn a(x2: ()) {} +} + +pub trait T { + /// **[`arg@x3`]** + //@ !has foo/trait.T.html '//strong a' 'x3' + //@ has foo/trait.T.html '//strong' 'x3' + fn a(x3: ()) {} + + /// **[arg@x4]** + //@ !has foo/trait.T.html '//strong a' 'x4' + //@ has foo/trait.T.html '//strong' 'x4' + fn b(x4: ()); +} + +extern "C" { + /// **[`arg@x5`]** + //@ !has foo/index.html '//strong a' 'x5' + //@ has foo/index.html '//strong' 'x5' + pub fn x(x5: ()); +} diff --git a/tests/rustdoc-ui/intra-doc/arg.rs b/tests/rustdoc-ui/intra-doc/arg.rs new file mode 100644 index 0000000000000..95d48731c44b8 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/arg.rs @@ -0,0 +1,34 @@ +#![deny(rustdoc::broken_intra_doc_links)] +#![feature(intra_doc_arg)] + +/// [arg@x] +//~^ ERROR argument `x` does not exist +pub fn a(y: ()) {} + +pub struct X; + +impl X { + /// [arg@x] + //~^ ERROR argument `x` does not exist + pub fn a(y: ()) {} +} + +pub trait T { + /// [arg@x] + //~^ ERROR argument `x` does not exist + fn a(y: ()) {} + + /// [arg@x] + //~^ ERROR argument `x` does not exist + fn b(y: ()); +} + +extern "C" { + /// [arg@x] + //~^ ERROR argument `x` does not exist + pub fn x(y: ()); +} + +/// [arg@a1] +//~^ ERROR can only be used in the documentation of functions +pub struct Y; diff --git a/tests/rustdoc-ui/intra-doc/arg.stderr b/tests/rustdoc-ui/intra-doc/arg.stderr new file mode 100644 index 0000000000000..d5b89e81e67ca --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/arg.stderr @@ -0,0 +1,44 @@ +error: argument `x` does not exist + --> $DIR/arg.rs:27:10 + | +LL | /// [arg@x] + | ^^^^^ + | +note: the lint level is defined here + --> $DIR/arg.rs:1:9 + | +LL | #![deny(rustdoc::broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: argument `x` does not exist + --> $DIR/arg.rs:4:6 + | +LL | /// [arg@x] + | ^^^^^ + +error: argument `x` does not exist + --> $DIR/arg.rs:11:10 + | +LL | /// [arg@x] + | ^^^^^ + +error: argument `x` does not exist + --> $DIR/arg.rs:17:10 + | +LL | /// [arg@x] + | ^^^^^ + +error: argument `x` does not exist + --> $DIR/arg.rs:21:10 + | +LL | /// [arg@x] + | ^^^^^ + +error: `arg@` disambiguator can only be used in the documentation of functions + --> $DIR/arg.rs:32:6 + | +LL | /// [arg@a1] + | ^^^^^^ + +error: aborting due to 6 previous errors + diff --git a/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.rs b/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.rs new file mode 100644 index 0000000000000..381e4d0bc6996 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.rs @@ -0,0 +1,5 @@ +#![deny(rustdoc::broken_intra_doc_links)] + +/// Hello [arg@x] +//~^ ERROR `arg@` disambiguators are experimental +pub fn a(x: ()) {} diff --git a/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.stderr b/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.stderr new file mode 100644 index 0000000000000..3308e26135727 --- /dev/null +++ b/tests/rustdoc-ui/intra-doc/feature-gate-intra-doc-arg.stderr @@ -0,0 +1,12 @@ +error[E0658]: `arg@` disambiguators are experimental + --> $DIR/feature-gate-intra-doc-arg.rs:3:12 + | +LL | /// Hello [arg@x] + | ^^^^^ + | + = help: add `#![feature(intra_doc_arg)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`.