From 4c765f66a4f7921b4a47ab21f699bb2d8290b44a Mon Sep 17 00:00:00 2001 From: Camelid Date: Thu, 8 Oct 2020 22:24:34 -0700 Subject: [PATCH 1/7] Allow generic parameters in intra-doc links The contents of the generics will be mostly ignored (except for warning if fully-qualified syntax is used, which is currently unsupported in intra-doc links - see issue #74563). * Allow links like `Vec`, `Result`, and `Option>` * Allow links like `Vec::::new()` * Warn on * Unbalanced angle brackets (e.g. `Vec>`) * Missing type to apply generics to (`` or `>`) * Use of fully-qualified syntax (`::into_iter`) * Invalid path separator (`Vec::new`) * Too many angle brackets (`Vec<>`) * Empty angle brackets (`Vec<>`) Note that this implementation *does* allow some constructs that aren't valid in the actual Rust syntax, for example `Box::new()`. That may not be supported in rustdoc in the future; it is an implementation detail. --- src/librustdoc/lib.rs | 2 + .../passes/collect_intra_doc_links.rs | 203 +++++++++++++++++- src/test/rustdoc-ui/intra-link-errors.rs | 2 +- .../intra-link-malformed-generics.rs | 19 ++ .../intra-link-malformed-generics.stderr | 102 +++++++++ .../rustdoc/intra-doc-link-generic-params.rs | 55 +++++ 6 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 src/test/rustdoc-ui/intra-link-malformed-generics.rs create mode 100644 src/test/rustdoc-ui/intra-link-malformed-generics.stderr create mode 100644 src/test/rustdoc/intra-doc-link-generic-params.rs diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index 7762e8f8d4f..12726d2bd9a 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -3,11 +3,13 @@ html_playground_url = "https://play.rust-lang.org/" )] #![feature(rustc_private)] +#![feature(array_methods)] #![feature(box_patterns)] #![feature(box_syntax)] #![feature(in_band_lifetimes)] #![feature(nll)] #![feature(or_patterns)] +#![feature(peekable_next_if)] #![feature(test)] #![feature(crate_visibility_modifier)] #![feature(never_type)] diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index b9be3e2f92b..33fa5a5033b 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -23,6 +23,7 @@ use smallvec::{smallvec, SmallVec}; use std::borrow::Cow; use std::cell::Cell; +use std::mem; use std::ops::Range; use crate::clean::*; @@ -65,10 +66,53 @@ enum ResolutionFailure<'a> { NotResolved { module_id: DefId, partial_res: Option, unresolved: Cow<'a, str> }, /// should not ever happen NoParentItem, + /// This link has malformed generic parameters; e.g., the angle brackets are unbalanced. + MalformedGenerics(MalformedGenerics), /// used to communicate that this should be ignored, but shouldn't be reported to the user Dummy, } +#[derive(Debug)] +enum MalformedGenerics { + /// This link has unbalanced angle brackets. + /// + /// For example, `Vec>`. + UnbalancedAngleBrackets, + /// The generics are not attached to a type. + /// + /// For example, `` should trigger this. + /// + /// This is detected by checking if the path is empty after the generics are stripped. + MissingType, + /// The link uses fully-qualified syntax, which is currently unsupported. + /// + /// For example, `::into_iter` should trigger this. + /// + /// This is detected by checking if ` as ` (the keyword `as` with spaces around it) is inside + /// angle brackets. + HasFullyQualifiedSyntax, + /// The link has an invalid path separator. + /// + /// For example, `Vec::new()` should trigger this. Note that `Vec:new()` will **not** + /// trigger this because it has no generics and thus [`strip_generics_from_path`] will not be + /// called. + /// + /// Note that this will also **not** be triggered if the invalid path separator is inside angle + /// brackets because rustdoc mostly ignores what's inside angle brackets (except for + /// [`HasFullyQualifiedSyntax`](MalformedGenerics::HasFullyQualifiedSyntax)). + /// + /// This is detected by checking if there is a colon followed by a non-colon in the link. + InvalidPathSeparator, + /// The link has too many angle brackets. + /// + /// For example, `Vec<>` should trigger this. + TooManyAngleBrackets, + /// The link has empty angle brackets. + /// + /// For example, `Vec<>` should trigger this. + EmptyAngleBrackets, +} + impl ResolutionFailure<'a> { // This resolved fully (not just partially) but is erroneous for some other reason fn full_res(&self) -> Option { @@ -912,6 +956,7 @@ impl LinkCollector<'_, '_> { let link_text; let mut path_str; let disambiguator; + let stripped_path_string; let (mut res, mut fragment) = { path_str = if let Ok((d, path)) = Disambiguator::from_str(&link) { disambiguator = Some(d); @@ -922,7 +967,7 @@ impl LinkCollector<'_, '_> { } .trim(); - if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ch == ':' || ch == '_')) { + if path_str.contains(|ch: char| !(ch.is_alphanumeric() || ":_<>, ".contains(ch))) { return None; } @@ -985,6 +1030,36 @@ impl LinkCollector<'_, '_> { module_id = DefId { krate, index: CRATE_DEF_INDEX }; } + // Strip generics from the path. + if path_str.contains(['<', '>'].as_slice()) { + stripped_path_string = match strip_generics_from_path(path_str) { + Ok(path) => path, + Err(err_kind) => { + debug!("link has malformed generics: {}", path_str); + resolution_failure( + self, + &item, + path_str, + disambiguator, + dox, + link_range, + smallvec![err_kind], + ); + return None; + } + }; + path_str = &stripped_path_string; + } + + // Sanity check to make sure we don't have any angle brackets after stripping generics. + assert!(!path_str.contains(['<', '>'].as_slice())); + + // The link is not an intra-doc link if it still contains commas or spaces after + // stripping generics. + if path_str.contains([',', ' '].as_slice()) { + return None; + } + match self.resolve_with_disambiguator( disambiguator, item, @@ -1718,6 +1793,27 @@ fn resolution_failure( diag.level = rustc_errors::Level::Bug; "all intra doc links should have a parent item".to_owned() } + ResolutionFailure::MalformedGenerics(variant) => match variant { + MalformedGenerics::UnbalancedAngleBrackets => { + String::from("unbalanced angle brackets") + } + MalformedGenerics::MissingType => { + String::from("missing type for generic parameters") + } + MalformedGenerics::HasFullyQualifiedSyntax => { + diag.note("see https://github.com/rust-lang/rust/issues/74563 for more information"); + String::from("fully-qualified syntax is unsupported") + } + MalformedGenerics::InvalidPathSeparator => { + String::from("has invalid path separator") + } + MalformedGenerics::TooManyAngleBrackets => { + String::from("too many angle brackets") + } + MalformedGenerics::EmptyAngleBrackets => { + String::from("empty angle brackets") + } + }, }; if let Some(span) = sp { diag.span_label(span, ¬e); @@ -1908,3 +2004,108 @@ fn is_primitive(path_str: &str, ns: Namespace) -> Option<(&'static str, Res)> { fn primitive_impl(cx: &DocContext<'_>, path_str: &str) -> Option<&'static SmallVec<[DefId; 4]>> { Some(PrimitiveType::from_symbol(Symbol::intern(path_str))?.impls(cx.tcx)) } + +fn strip_generics_from_path(path_str: &str) -> Result> { + let mut stripped_segments = vec![]; + let mut path = path_str.chars().peekable(); + let mut segment = Vec::new(); + + while let Some(chr) = path.next() { + match chr { + ':' => { + if path.next_if_eq(&':').is_some() { + let stripped_segment = + strip_generics_from_path_segment(mem::take(&mut segment))?; + if !stripped_segment.is_empty() { + stripped_segments.push(stripped_segment); + } + } else { + return Err(ResolutionFailure::MalformedGenerics( + MalformedGenerics::InvalidPathSeparator, + )); + } + } + '<' => { + segment.push(chr); + + match path.peek() { + Some('<') => { + return Err(ResolutionFailure::MalformedGenerics( + MalformedGenerics::TooManyAngleBrackets, + )); + } + Some('>') => { + return Err(ResolutionFailure::MalformedGenerics( + MalformedGenerics::EmptyAngleBrackets, + )); + } + Some(_) => { + segment.push(path.next().unwrap()); + + while let Some(chr) = path.next_if(|c| *c != '>') { + segment.push(chr); + } + } + None => break, + } + } + _ => segment.push(chr), + } + debug!("raw segment: {:?}", segment); + } + + if !segment.is_empty() { + let stripped_segment = strip_generics_from_path_segment(segment)?; + if !stripped_segment.is_empty() { + stripped_segments.push(stripped_segment); + } + } + + debug!("path_str: {:?}\nstripped segments: {:?}", path_str, &stripped_segments); + + let stripped_path = stripped_segments.join("::"); + + if !stripped_path.is_empty() { + Ok(stripped_path) + } else { + Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::MissingType)) + } +} + +fn strip_generics_from_path_segment( + segment: Vec, +) -> Result> { + let mut stripped_segment = String::new(); + let mut param_depth = 0; + + let mut latest_generics_chunk = String::new(); + + for c in segment { + if c == '<' { + param_depth += 1; + latest_generics_chunk.clear(); + } else if c == '>' { + param_depth -= 1; + if latest_generics_chunk.contains(" as ") { + // The segment tries to use fully-qualified syntax, which is currently unsupported. + // Give a helpful error message instead of completely ignoring the angle brackets. + return Err(ResolutionFailure::MalformedGenerics( + MalformedGenerics::HasFullyQualifiedSyntax, + )); + } + } else { + if param_depth == 0 { + stripped_segment.push(c); + } else { + latest_generics_chunk.push(c); + } + } + } + + if param_depth == 0 { + Ok(stripped_segment) + } else { + // The segment has unbalanced angle brackets, e.g. `Vec>` + Err(ResolutionFailure::MalformedGenerics(MalformedGenerics::UnbalancedAngleBrackets)) + } +} diff --git a/src/test/rustdoc-ui/intra-link-errors.rs b/src/test/rustdoc-ui/intra-link-errors.rs index ef928ae02f3..81e42643ae8 100644 --- a/src/test/rustdoc-ui/intra-link-errors.rs +++ b/src/test/rustdoc-ui/intra-link-errors.rs @@ -2,7 +2,7 @@ //~^ NOTE lint level is defined // FIXME: this should say that it was skipped (maybe an allowed by default lint?) -/// [] +/// [invalid intra-doc syntax!!] /// [path::to::nonexistent::module] //~^ ERROR unresolved link diff --git a/src/test/rustdoc-ui/intra-link-malformed-generics.rs b/src/test/rustdoc-ui/intra-link-malformed-generics.rs new file mode 100644 index 00000000000..9c54092146f --- /dev/null +++ b/src/test/rustdoc-ui/intra-link-malformed-generics.rs @@ -0,0 +1,19 @@ +#![deny(broken_intra_doc_links)] + +//! [Vec<] //~ ERROR +//! [Vec] //~ ERROR +//! [Vec>>] //~ ERROR +//! [Vec>>] //~ ERROR +//! [] //~ ERROR +//! [] //~ ERROR +//! [Vec::new()] //~ ERROR +//! [Vec<>] //~ ERROR +//! [Vec<>] //~ ERROR +//! [Vec<<>>] //~ ERROR + +// FIXME(#74563) support UFCS +//! [::into_iter] //~ ERROR +//! [ as IntoIterator>::iter] //~ ERROR diff --git a/src/test/rustdoc-ui/intra-link-malformed-generics.stderr b/src/test/rustdoc-ui/intra-link-malformed-generics.stderr new file mode 100644 index 00000000000..fe5d4cd1bbf --- /dev/null +++ b/src/test/rustdoc-ui/intra-link-malformed-generics.stderr @@ -0,0 +1,102 @@ +error: unresolved link to `Vec<` + --> $DIR/intra-link-malformed-generics.rs:3:6 + | +LL | //! [Vec<] + | ^^^^ unbalanced angle brackets + | +note: the lint level is defined here + --> $DIR/intra-link-malformed-generics.rs:1:9 + | +LL | #![deny(broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: unresolved link to `Vec $DIR/intra-link-malformed-generics.rs:4:6 + | +LL | //! [Vec` + --> $DIR/intra-link-malformed-generics.rs:5:6 + | +LL | //! [Vec] + | ^^^^^^^^^^ unbalanced angle brackets + +error: unresolved link to `Vec>>` + --> $DIR/intra-link-malformed-generics.rs:6:6 + | +LL | //! [Vec>>] + | ^^^^^^^^^^^^ unbalanced angle brackets + +error: unresolved link to `Vec>>` + --> $DIR/intra-link-malformed-generics.rs:7:6 + | +LL | //! [Vec>>] + | ^^^^^^^^ unbalanced angle brackets + +error: unresolved link to ` $DIR/intra-link-malformed-generics.rs:8:6 + | +LL | //! [ $DIR/intra-link-malformed-generics.rs:9:6 + | +LL | //! [Vec::<] + | ^^^^^^ unbalanced angle brackets + +error: unresolved link to `` + --> $DIR/intra-link-malformed-generics.rs:10:6 + | +LL | //! [] + | ^^^ missing type for generic parameters + +error: unresolved link to `` + --> $DIR/intra-link-malformed-generics.rs:11:6 + | +LL | //! [] + | ^^^^^^^^^^^^^^^^ missing type for generic parameters + +error: unresolved link to `Vec::new` + --> $DIR/intra-link-malformed-generics.rs:12:6 + | +LL | //! [Vec::new()] + | ^^^^^^^^^^^^^ has invalid path separator + +error: unresolved link to `Vec<>` + --> $DIR/intra-link-malformed-generics.rs:13:6 + | +LL | //! [Vec<>] + | ^^^^^^^^ too many angle brackets + +error: unresolved link to `Vec<>` + --> $DIR/intra-link-malformed-generics.rs:14:6 + | +LL | //! [Vec<>] + | ^^^^^ empty angle brackets + +error: unresolved link to `Vec<<>>` + --> $DIR/intra-link-malformed-generics.rs:15:6 + | +LL | //! [Vec<<>>] + | ^^^^^^^ too many angle brackets + +error: unresolved link to `::into_iter` + --> $DIR/intra-link-malformed-generics.rs:18:6 + | +LL | //! [::into_iter] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fully-qualified syntax is unsupported + | + = note: see https://github.com/rust-lang/rust/issues/74563 for more information + +error: unresolved link to ` as IntoIterator>::iter` + --> $DIR/intra-link-malformed-generics.rs:19:6 + | +LL | //! [ as IntoIterator>::iter] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ fully-qualified syntax is unsupported + | + = note: see https://github.com/rust-lang/rust/issues/74563 for more information + +error: aborting due to 15 previous errors + diff --git a/src/test/rustdoc/intra-doc-link-generic-params.rs b/src/test/rustdoc/intra-doc-link-generic-params.rs new file mode 100644 index 00000000000..06735752974 --- /dev/null +++ b/src/test/rustdoc/intra-doc-link-generic-params.rs @@ -0,0 +1,55 @@ +// ignore-tidy-linelength + +#![crate_name = "foo"] + +//! Here's a link to [`Vec`] and one to [`Box>>`]. +//! Here's a link to [`Iterator>::Item`]. +//! +//! And what about a link to [just `Option`](Option) and, [with the generic, `Option`](Option)? +//! +//! We should also try linking to [`Result`]; it has *two* generics! +//! +//! Now let's test a trickier case: [`Vec::::new`], or you could write it +//! [with parentheses as `Vec::::new()`][Vec::::new()]. +//! And what about something even harder? That would be [`Vec::>::new()`]. +//! +//! This is also pretty tricky: [`TypeId::of::()`]. +//! And this too: [`Vec::::len`]. +//! +//! We unofficially and implicitly support things that aren't valid in the actual Rust syntax, like +//! [`Box::new()`]. We may not support them in the future! +//! +//! These will be resolved as regular links: +//! - [`this is first`](https://www.rust-lang.org) +//! - [`this is twice`] +//! - [` thrice`](https://www.rust-lang.org) +//! - [` four times`][rlo] +//! - [a < b][rlo] +//! - [c > d] +//! +//! [`this is twice`]: https://www.rust-lang.org +//! [rlo]: https://www.rust-lang.org +//! [c > d]: https://www.rust-lang.org + +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html"]' 'Vec' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html"]' 'Box>>' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item"]' 'Iterator>::Item' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'just Option' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'with the generic, Option' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/result/enum.Result.html"]' 'Result' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::::new' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'with parentheses as Vec::::new()' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::>::new()' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/any/struct.TypeId.html#method.of"]' 'TypeId::of::()' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.len"]' 'Vec::::len' + +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.new"]' 'Box::new()' + +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'this is first' +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'this is twice' +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' ' thrice' +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' ' four times' +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'a < b' +// @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'c > d' + +use std::any::TypeId; From 6df21a326e85c6ebfd38d32a68255f7ddd4a0d11 Mon Sep 17 00:00:00 2001 From: Camelid Date: Sun, 27 Sep 2020 14:51:45 -0700 Subject: [PATCH 2/7] Fix intra-doc links in `core` Caught by my malformed generics diagnostics! --- library/core/src/option.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/library/core/src/option.rs b/library/core/src/option.rs index 0cfb4af59b9..9cb20a0afdc 100644 --- a/library/core/src/option.rs +++ b/library/core/src/option.rs @@ -141,6 +141,9 @@ //! ``` //! //! [`Box`]: ../../std/boxed/struct.Box.html +//! [`Box`]: ../../std/boxed/struct.Box.html +//! [`num::NonZero*`]: crate::num +//! [`ptr::NonNull`]: crate::ptr::NonNull #![stable(feature = "rust1", since = "1.0.0")] From b9c299effd1f48eeb0474375e2a466f53776f294 Mon Sep 17 00:00:00 2001 From: Camelid Date: Sat, 3 Oct 2020 15:44:53 -0700 Subject: [PATCH 3/7] Update rustdoc intra-doc link docs * Describe generic parameters feature * Make general improvements to the docs --- src/doc/rustdoc/src/SUMMARY.md | 2 +- src/doc/rustdoc/src/advanced-features.md | 2 +- .../rustdoc/src/linking-to-items-by-name.md | 39 ++++++++++++++++--- src/doc/rustdoc/src/lints.md | 10 ++--- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/doc/rustdoc/src/SUMMARY.md b/src/doc/rustdoc/src/SUMMARY.md index 93454b4f909..a3fa525be1d 100644 --- a/src/doc/rustdoc/src/SUMMARY.md +++ b/src/doc/rustdoc/src/SUMMARY.md @@ -8,5 +8,5 @@ - [Linking to items by name](linking-to-items-by-name.md) - [Lints](lints.md) - [Passes](passes.md) -- [Advanced Features](advanced-features.md) +- [Advanced features](advanced-features.md) - [Unstable features](unstable-features.md) diff --git a/src/doc/rustdoc/src/advanced-features.md b/src/doc/rustdoc/src/advanced-features.md index 8be9489c617..5128ff13b7a 100644 --- a/src/doc/rustdoc/src/advanced-features.md +++ b/src/doc/rustdoc/src/advanced-features.md @@ -1,4 +1,4 @@ -# Advanced Features +# Advanced features The features listed on this page fall outside the rest of the main categories. diff --git a/src/doc/rustdoc/src/linking-to-items-by-name.md b/src/doc/rustdoc/src/linking-to-items-by-name.md index 5e46ef583f6..26bee2974ac 100644 --- a/src/doc/rustdoc/src/linking-to-items-by-name.md +++ b/src/doc/rustdoc/src/linking-to-items-by-name.md @@ -1,6 +1,7 @@ # Linking to items by name -Rustdoc is capable of directly linking to other rustdoc pages in Markdown documentation using the path of item as a link. +Rustdoc is capable of directly linking to other rustdoc pages using the path of +the item as a link. For example, in the following code all of the links will link to the rustdoc page for `Bar`: @@ -19,15 +20,26 @@ pub struct Foo3; /// This struct is also not [`Bar`] pub struct Foo4; +/// This struct *is* [`Bar`]! pub struct Bar; ``` -You can refer to anything in scope, and use paths, including `Self`, `self`, `super`, and `crate`. You may also use `foo()` and `foo!()` to refer to methods/functions and macros respectively. Backticks around the link will be stripped. +Backticks around the link will be stripped, so ``[`Option`]`` will correctly +link to `Option`. + +You can refer to anything in scope, and use paths, including `Self`, `self`, +`super`, and `crate`. You may also use `foo()` and `foo!()` to refer to methods/functions and macros, respectively. + +You can also refer to items with generic parameters like `Vec`. The link will +resolve as if you had written ``[`Vec`](Vec)``. Fully-qualified syntax (for example, +`::into_iter()`) is [not yet supported][fqs-issue], however. + +[fqs-issue]: https://github.com/rust-lang/rust/issues/74563 ```rust,edition2018 use std::sync::mpsc::Receiver; -/// This is an version of [`Receiver`], with support for [`std::future`]. +/// This is a version of [`Receiver`] with support for [`std::future`]. /// /// You can obtain a [`std::future::Future`] by calling [`Self::recv()`]. pub struct AsyncReceiver { @@ -44,13 +56,15 @@ impl AsyncReceiver { You can also link to sections using URL fragment specifiers: ```rust -/// This is a special implementation of [positional parameters] +/// This is a special implementation of [positional parameters]. /// /// [positional parameters]: std::fmt#formatting-parameters struct MySpecialFormatter; ``` -Paths in Rust have three namespaces: type, value, and macro. Items from these namespaces are allowed to overlap. In case of ambiguity, rustdoc will warn about the ambiguity and ask you to disambiguate, which can be done by using a prefix like `struct@`, `enum@`, `type@`, `trait@`, `union@`, `const@`, `static@`, `value@`, `function@`, `mod@`, `fn@`, `module@`, `method@`, `prim@`, `primitive@`, `macro@`, or `derive@`: +Paths in Rust have three namespaces: type, value, and macro. Item names must be +unique within their namespace, but can overlap with items outside of their +namespace. In case of ambiguity, rustdoc will warn about the ambiguity and ask you to disambiguate, which can be done by using a prefix like `struct@`, `enum@`, `type@`, `trait@`, `union@`, `const@`, `static@`, `value@`, `fn@`, `function@`, `mod@`, `module@`, `method@`, `prim@`, `primitive@`, `macro@`, or `derive@`: ```rust /// See also: [`Foo`](struct@Foo) @@ -62,4 +76,17 @@ struct Foo {} fn Foo() {} ``` -Note: Because of how `macro_rules` macros are scoped in Rust, the intra-doc links of a `macro_rules` macro will be resolved relative to the crate root, as opposed to the module it is defined in. +You can also disambiguate for functions by adding `()` after the function name, +or for macros by adding `!` after the macro name: + +```rust +/// See also: [`Foo`](struct@Foo) +struct Bar; + +/// This is different from [`Foo()`] +struct Foo {} + +fn Foo() {} +``` + +Note: Because of how `macro_rules!` macros are scoped in Rust, the intra-doc links of a `macro_rules!` macro will be resolved relative to the crate root, as opposed to the module it is defined in. diff --git a/src/doc/rustdoc/src/lints.md b/src/doc/rustdoc/src/lints.md index d8c0bab2259..cb9099cd50b 100644 --- a/src/doc/rustdoc/src/lints.md +++ b/src/doc/rustdoc/src/lints.md @@ -4,18 +4,18 @@ can use them like any other lints by doing this: ```rust,ignore -#![allow(missing_docs)] // allowing the lint, no message -#![warn(missing_docs)] // warn if there is missing docs -#![deny(missing_docs)] // rustdoc will fail if there is missing docs +#![allow(missing_docs)] // allows the lint, no diagnostics will be reported +#![warn(missing_docs)] // warn if there are missing docs +#![deny(missing_docs)] // error if there are missing docs ``` Here is the list of the lints provided by `rustdoc`: ## broken_intra_doc_links -This lint **warns by default**. This lint detects when an [intra-doc link] fails to get resolved. For example: +This lint **warns by default**. This lint detects when an [intra-doc link] fails to be resolved. For example: - [intra-doc link]: linking-to-items-by-name.html +[intra-doc link]: linking-to-items-by-name.md ```rust /// I want to link to [`Nonexistent`] but it doesn't exist! From 330ce948f7a0c338ba669bfe904a9619f9589c9e Mon Sep 17 00:00:00 2001 From: Camelid Date: Fri, 9 Oct 2020 16:03:00 -0700 Subject: [PATCH 4/7] Link to GitHub issue re macro resolution Co-authored-by: Joshua Nelson --- src/doc/rustdoc/src/linking-to-items-by-name.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/doc/rustdoc/src/linking-to-items-by-name.md b/src/doc/rustdoc/src/linking-to-items-by-name.md index 26bee2974ac..76e04398530 100644 --- a/src/doc/rustdoc/src/linking-to-items-by-name.md +++ b/src/doc/rustdoc/src/linking-to-items-by-name.md @@ -89,4 +89,6 @@ struct Foo {} fn Foo() {} ``` -Note: Because of how `macro_rules!` macros are scoped in Rust, the intra-doc links of a `macro_rules!` macro will be resolved relative to the crate root, as opposed to the module it is defined in. +Note: Because of how `macro_rules!` macros are scoped in Rust, the intra-doc links of a `macro_rules!` macro will be resolved [relative to the crate root][#72243], as opposed to the module it is defined in. + +[#72243]: https://github.com/rust-lang/rust/issues/72243 From 71ca8840d54a4a35ab51ffdf9e6e4f5469abcf77 Mon Sep 17 00:00:00 2001 From: Camelid Date: Fri, 9 Oct 2020 16:08:15 -0700 Subject: [PATCH 5/7] Use `next()` instead of `peek()` where possible --- src/librustdoc/passes/collect_intra_doc_links.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 33fa5a5033b..4b455c6fea7 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -2028,7 +2028,7 @@ fn strip_generics_from_path(path_str: &str) -> Result { segment.push(chr); - match path.peek() { + match path.next() { Some('<') => { return Err(ResolutionFailure::MalformedGenerics( MalformedGenerics::TooManyAngleBrackets, @@ -2039,8 +2039,8 @@ fn strip_generics_from_path(path_str: &str) -> Result { - segment.push(path.next().unwrap()); + Some(chr) => { + segment.push(chr); while let Some(chr) = path.next_if(|c| *c != '>') { segment.push(chr); From 5883d3de9c5536a3bdd69959fbf6e74ebccda729 Mon Sep 17 00:00:00 2001 From: Camelid Date: Fri, 9 Oct 2020 16:11:15 -0700 Subject: [PATCH 6/7] Move `@has` checks closer to corresponding doc comments --- .../rustdoc/intra-doc-link-generic-params.rs | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/test/rustdoc/intra-doc-link-generic-params.rs b/src/test/rustdoc/intra-doc-link-generic-params.rs index 06735752974..7d7289437ff 100644 --- a/src/test/rustdoc/intra-doc-link-generic-params.rs +++ b/src/test/rustdoc/intra-doc-link-generic-params.rs @@ -5,20 +5,38 @@ //! Here's a link to [`Vec`] and one to [`Box>>`]. //! Here's a link to [`Iterator>::Item`]. //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html"]' 'Vec' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html"]' 'Box>>' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item"]' 'Iterator>::Item' + //! And what about a link to [just `Option`](Option) and, [with the generic, `Option`](Option)? //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'just Option' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'with the generic, Option' + //! We should also try linking to [`Result`]; it has *two* generics! //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/result/enum.Result.html"]' 'Result' + //! Now let's test a trickier case: [`Vec::::new`], or you could write it //! [with parentheses as `Vec::::new()`][Vec::::new()]. //! And what about something even harder? That would be [`Vec::>::new()`]. //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::::new' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'with parentheses as Vec::::new()' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::>::new()' + //! This is also pretty tricky: [`TypeId::of::()`]. //! And this too: [`Vec::::len`]. //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/any/struct.TypeId.html#method.of"]' 'TypeId::of::()' +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.len"]' 'Vec::::len' + //! We unofficially and implicitly support things that aren't valid in the actual Rust syntax, like //! [`Box::new()`]. We may not support them in the future! //! +// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.new"]' 'Box::new()' + //! These will be resolved as regular links: //! - [`this is first`](https://www.rust-lang.org) //! - [`this is twice`] @@ -30,21 +48,7 @@ //! [`this is twice`]: https://www.rust-lang.org //! [rlo]: https://www.rust-lang.org //! [c > d]: https://www.rust-lang.org - -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html"]' 'Vec' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html"]' 'Box>>' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/iter/traits/iterator/trait.Iterator.html#associatedtype.Item"]' 'Iterator>::Item' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'just Option' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/option/enum.Option.html"]' 'with the generic, Option' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/result/enum.Result.html"]' 'Result' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::::new' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'with parentheses as Vec::::new()' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.new"]' 'Vec::>::new()' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/core/any/struct.TypeId.html#method.of"]' 'TypeId::of::()' -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/vec/struct.Vec.html#method.len"]' 'Vec::::len' - -// @has foo/index.html '//a[@href="https://doc.rust-lang.org/nightly/alloc/boxed/struct.Box.html#method.new"]' 'Box::new()' - +//! // @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'this is first' // @has foo/index.html '//a[@href="https://www.rust-lang.org"]' 'this is twice' // @has foo/index.html '//a[@href="https://www.rust-lang.org"]' ' thrice' From e2424a2c1f4d5502b8900a1701c7a8a3a2a28bf9 Mon Sep 17 00:00:00 2001 From: Camelid Date: Sat, 10 Oct 2020 12:49:31 -0700 Subject: [PATCH 7/7] Fix query docs They were not formatted correctly, so rustdoc was interpreting some parts as code. Also cleaned up some other query docs that weren't causing issues, but were formatted incorrectly. --- compiler/rustc_middle/src/query/mod.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index fc4c343372a..1acb44f6d22 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -92,7 +92,7 @@ rustc_queries! { /// Computes the `DefId` of the corresponding const parameter in case the `key` is a /// const argument and returns `None` otherwise. /// - /// ```rust + /// ```ignore (incomplete) /// let a = foo::<7>(); /// // ^ Calling `opt_const_param_of` for this argument, /// @@ -162,10 +162,12 @@ rustc_queries! { /// Specifically this is the bounds written on the trait's type /// definition, or those after the `impl` keyword /// + /// ```ignore (incomplete) /// type X: Bound + 'lt - /// ^^^^^^^^^^^ + /// // ^^^^^^^^^^^ /// impl Debug + Display - /// ^^^^^^^^^^^^^^^ + /// // ^^^^^^^^^^^^^^^ + /// ``` /// /// `key` is the `DefId` of the associated type or opaque type. /// @@ -176,18 +178,22 @@ rustc_queries! { /// Elaborated version of the predicates from `explicit_item_bounds`. /// - /// Example for + /// For example: /// + /// ``` /// trait MyTrait { - /// type MyAType: Eq + ?Sized` + /// type MyAType: Eq + ?Sized; /// } + /// ``` /// /// `explicit_item_bounds` returns `[::MyAType: Eq]`, /// and `item_bounds` returns + /// ```text /// [ /// ::MyAType: Eq, /// ::MyAType: PartialEq<::MyAType> /// ] + /// ``` /// /// Bounds from the parent (e.g. with nested impl trait) are not included. query item_bounds(key: DefId) -> &'tcx ty::List> {