diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1e3260ce9ae2b..8dc0ae40f10d7 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1890,6 +1890,7 @@ fn clean_maybe_renamed_item( ) -> Vec { use hir::ItemKind; + debug!("clean HIR item {:?}", item); let def_id = item.def_id.to_def_id(); let mut name = renamed.unwrap_or_else(|| cx.tcx.hir().name(item.hir_id())); cx.with_param_env(def_id, |cx| { @@ -2017,6 +2018,7 @@ fn clean_impl(impl_: &hir::Impl<'_>, hir_id: hir::HirId, cx: &mut DocContext<'_> ret.push(make_item(trait_.clone(), type_alias, items.clone())); } ret.push(make_item(trait_, for_, items)); + debug!("create cleaned impl item {:?}", ret); ret } diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 47289eb8978b9..48a0d470a3dc5 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::cell::RefCell; use std::default::Default; use std::hash::Hash; @@ -351,7 +352,7 @@ crate enum ExternalLocation { /// Anything with a source location and set of attributes and, optionally, a /// name. That is, anything that can be documented. This doesn't correspond /// directly to the AST's concept of an item; it's a strict superset. -#[derive(Clone, Debug)] +#[derive(Clone)] crate struct Item { /// The name of this item. /// Optional because not every item has a name, e.g. impls. @@ -366,6 +367,27 @@ crate struct Item { crate cfg: Option>, } +impl fmt::Debug for Item { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let alternate = f.alternate(); + // hand-picked fields that don't bloat the logs too much + let mut fmt = f.debug_struct("Item"); + fmt.field("name", &self.name) + .field("visibility", &self.visibility) + .field("def_id", &self.def_id); + // allow printing the full item if someone really wants to + if alternate { + fmt.field("attrs", &self.attrs).field("kind", &self.kind).field("cfg", &self.cfg); + } else { + fmt.field("kind", &self.type_()); + if let Some(docs) = self.doc_value() { + fmt.field("docs", &docs); + } + } + fmt.finish() + } +} + // `Item` is used a lot. Make sure it doesn't unintentionally get bigger. #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] rustc_data_structures::static_assert_size!(Item, 56); @@ -1101,11 +1123,16 @@ impl Attributes { ast::LitKind::Str(s, _) => { aliases.insert(s); } - _ => unreachable!(), + // This can be reached in case of an invalid attribute. + // We test that rustc_passes gives an error in `src/test/rustdoc-ui/check-doc-alias-attr.rs` + _ => {} } } - } else { - aliases.insert(attr.value_str().unwrap()); + // This can be None if the user specified an invalid attribute, e.g. `doc(alias = 0)`. + // The error will be caught by rustc_passes, but we still need to handle it here because + // the Cache is populated before calling `abort_if_err()`. + } else if let Some(value) = attr.value_str() { + aliases.insert(value); } } aliases.into_iter().collect::>().into() diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index cee3dcb416f80..480d72076b773 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -339,14 +339,14 @@ impl Options { println!("{:>20} - {}", pass.name, pass.description); } println!("\nDefault passes for rustdoc:"); - for p in passes::DEFAULT_PASSES { + for &p in passes::DEFAULT_PASSES.0.iter().chain(passes::DEFAULT_PASSES.1) { print!("{:>20}", p.pass.name); println_condition(p.condition); } if nightly_options::match_is_nightly_build(matches) { println!("\nPasses run with `--show-coverage`:"); - for p in passes::COVERAGE_PASSES { + for &p in passes::COVERAGE_PASSES.0.iter().chain(passes::COVERAGE_PASSES.1) { print!("{:>20}", p.pass.name); println_condition(p.condition); } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index bd64e2b03ce0c..1c7ccdd06cb01 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -25,10 +25,10 @@ use std::mem; use std::rc::Rc; use crate::clean::inline::build_external_trait; -use crate::clean::{self, ItemId, TraitWithExtraInfo}; +use crate::clean::{self, Crate, ItemId, TraitWithExtraInfo}; use crate::config::{Options as RustdocOptions, OutputFormat, RenderOptions}; use crate::formats::cache::Cache; -use crate::passes::{self, Condition::*}; +use crate::passes::{self, Condition::*, ConditionalPass}; crate use rustc_session::config::{DebuggingOptions, Input, Options}; @@ -438,8 +438,7 @@ crate fn run_global_ctxt( } info!("Executing passes"); - - for p in passes::defaults(show_coverage) { + let run_pass = |p: ConditionalPass, krate: Crate, ctxt: &mut DocContext<'_>| { let run = match p.condition { Always => true, WhenDocumentPrivate => ctxt.render_options.document_private, @@ -448,15 +447,27 @@ crate fn run_global_ctxt( }; if run { debug!("running pass {}", p.pass.name); - krate = tcx.sess.time(p.pass.name, || (p.pass.run)(krate, &mut ctxt)); + tcx.sess.time(p.pass.name, || (p.pass.run)(krate, ctxt)) + } else { + krate } + }; + + let (passes_before_cache, passes_after_cache) = passes::defaults(show_coverage); + for &p in passes_before_cache { + krate = run_pass(p, krate, &mut ctxt); + } + + krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); + for &p in passes_after_cache { + krate = run_pass(p, krate, &mut ctxt); } if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() { rustc_errors::FatalError.raise(); } - krate = tcx.sess.time("create_format_cache", || Cache::populate(&mut ctxt, krate)); + krate = ctxt.cache.populate_impls(krate); (krate, ctxt.render_options, ctxt.cache) } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 53159709586c6..41f5d571e7b14 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -179,9 +179,29 @@ impl Cache { } } } + // god what a mess + *krate.external_traits.borrow_mut() = mem::take(&mut cx.cache.traits); krate } + + /// `external_trats` / `cache.traits` is modified in various passes. + /// Run this separate from the main `populate` call, since `impls` isn't used until later in the HTML formatter. + crate fn populate_impls(&mut self, krate: clean::Crate) -> clean::Crate { + self.traits = krate.external_traits.take(); + ImplRemover.fold_crate(krate) + } +} + +struct ImplRemover; +impl DocFolder for ImplRemover { + fn fold_item(&mut self, item: clean::Item) -> Option { + let item = self.fold_item_recur(item); + match *item.kind { + clean::ItemKind::ImplItem(_) => None, + _ => Some(item), + } + } } impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { @@ -210,6 +230,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { .def_id(self.cache) .map_or(false, |d| self.cache.masked_crates.contains(&d.krate)) { + debug!("remove masked impl {:?}", i); return None; } } @@ -429,7 +450,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // Once we've recursively found all the generics, hoard off all the // implementations elsewhere. let item = self.fold_item_recur(item); - let ret = if let clean::Item { kind: box clean::ImplItem(ref i), .. } = item { + if let clean::Item { kind: box clean::ImplItem(ref i), .. } = item { // Figure out the id of this impl. This may map to a // primitive rather than always to a struct/enum. // Note: matching twice to restrict the lifetime of the `i` borrow. @@ -461,19 +482,16 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { } } } - let impl_item = Impl { impl_item: item }; + let impl_item = Impl { impl_item: item.clone() }; if impl_item.trait_did().map_or(true, |d| self.cache.traits.contains_key(&d)) { for did in dids { self.cache.impls.entry(did).or_insert_with(Vec::new).push(impl_item.clone()); } } else { let trait_did = impl_item.trait_did().expect("no trait did"); - self.cache.orphan_trait_impls.push((trait_did, dids, impl_item)); + self.cache.orphan_trait_impls.push((trait_did, dids, impl_item.clone())); } - None - } else { - Some(item) - }; + } if pushed { self.cache.stack.pop().expect("stack already empty"); @@ -483,6 +501,6 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { } self.cache.stripped_mod = orig_stripped_mod; self.cache.parent_is_trait_impl = orig_parent_is_trait_impl; - ret + Some(item) } } diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 5c59609d5b8c6..ba912a7795f53 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -26,6 +26,7 @@ use crate::clean::{ self, types::ExternalLocation, utils::find_nearest_parent_module, ExternalCrate, ItemId, PrimitiveType, }; +use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; use crate::html::escape::Escape; use crate::html::render::Context; @@ -523,6 +524,7 @@ impl clean::GenericArgs { } // Possible errors when computing href link source for a `DefId` +#[derive(Debug, PartialEq, Eq)] crate enum HrefError { /// This item is known to rustdoc, but from a crate that does not have documentation generated. /// @@ -551,20 +553,31 @@ crate fn href_with_root_path( root_path: Option<&str>, ) -> Result<(String, ItemType, Vec), HrefError> { let tcx = cx.tcx(); + let cache = cx.cache(); + let relative_to = &cx.current; + href_inner(did, tcx, cache, root_path, relative_to) +} + +crate fn href_inner( + did: DefId, + tcx: TyCtxt<'_>, + cache: &Cache, + root_path: Option<&str>, + relative_to: &[Symbol], +) -> Result<(String, ItemType, Vec), HrefError> { let def_kind = tcx.def_kind(did); let did = match def_kind { - DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant => { + DefKind::AssocTy | DefKind::AssocFn | DefKind::AssocConst | DefKind::Variant | DefKind::Field => { // documented on their parent's page tcx.parent(did).unwrap() } _ => did, }; - let cache = cx.cache(); - let relative_to = &cx.current; fn to_module_fqp(shortty: ItemType, fqp: &[Symbol]) -> &[Symbol] { if shortty == ItemType::Module { fqp } else { &fqp[..fqp.len() - 1] } } + trace!("calculating href for {:?}", did); if !did.is_local() && !cache.access_levels.is_public(did) && !cache.document_private diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 3d8a62d50e06d..2f968e5a3c206 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -26,15 +26,20 @@ use std::fmt::Write; use std::mem; use std::ops::Range; -use crate::clean::{self, utils::find_nearest_parent_module}; use crate::clean::{Crate, Item, ItemId, ItemLink, PrimitiveType}; use crate::core::DocContext; -use crate::html::markdown::{markdown_links, MarkdownLink}; -use crate::lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}; -use crate::passes::Pass; -use crate::visit::DocVisitor; +use crate::{ + clean::{self, utils::find_nearest_parent_module}, + html::{ + format::HrefError, + markdown::{markdown_links, MarkdownLink}, + }, + lint::{BROKEN_INTRA_DOC_LINKS, PRIVATE_INTRA_DOC_LINKS}, + visit::DocVisitor, +}; mod early; +use super::Pass; crate use early::early_resolve_intra_doc_links; crate const COLLECT_INTRA_DOC_LINKS: Pass = Pass { @@ -44,6 +49,7 @@ crate const COLLECT_INTRA_DOC_LINKS: Pass = Pass { }; fn collect_intra_doc_links(krate: Crate, cx: &mut DocContext<'_>) -> Crate { + debug!("logging works"); let mut collector = LinkCollector { cx, mod_ids: Vec::new(), visited_links: FxHashMap::default() }; collector.visit_crate(&krate); @@ -1413,6 +1419,7 @@ impl LinkCollector<'_, '_> { } } + let mut had_error = false; // item can be non-local e.g. when using #[doc(primitive = "pointer")] if let Some((src_id, dst_id)) = id .as_local() @@ -1426,9 +1433,23 @@ impl LinkCollector<'_, '_> { && !self.cx.tcx.privacy_access_levels(()).is_exported(dst_id) { privacy_error(self.cx, diag_info, path_str); + had_error = true; } } + // Even if the item exists and is public, it may not have documentation generated + // (e.g. if it's marked with `doc(hidden)`, or in a dependency with `--no-deps`). + // Give a warning if so. + if had_error { + return Some(()); + } + // `relative_to` is only used for the actual path of the link, not whether it's resolved or not. + if let Err(missing) = + crate::html::format::href_inner(id, self.cx.tcx, &self.cx.cache, None, &[]) + { + missing_docs_for_link(self.cx, &item, id, missing, &path_str, &diag_info); + } + Some(()) } @@ -2294,6 +2315,84 @@ fn suggest_disambiguator( } } +/// Report a link to an item that will not have documentation generated. +fn missing_docs_for_link( + cx: &DocContext<'_>, + item: &Item, + destination_id: DefId, + why: HrefError, + path_str: &str, + diag_info: &DiagnosticInfo<'_>, +) { + debug!("missing docs for {:?} because {:?}", item, why); + // FIXME: this is a bug, rustdoc should load all items into the cache before doing this check. + // Otherwise, there will be false negatives where rustdoc doesn't warn but should. + // if why == HrefError::NotInExternalCache { + // debug!("ignoring NotInExternalCache"); + // return; + // } + + let item_name = match item.name { + Some(name) => name.to_string(), + None => "".into(), + }; + let msg = format!( + "documentation for `{}` links to item `{}` which will not have documentation generated", + item_name, path_str + ); + + report_diagnostic(cx.tcx, PRIVATE_INTRA_DOC_LINKS, &msg, diag_info, |diag, sp| { + use crate::clean::types::{AttributesExt, NestedAttributesExt}; + + if let Some(sp) = sp { + diag.span_label(sp, "this item is will not be documented"); + } + + if why == HrefError::DocumentationNotBuilt { + diag.note(&format!( + "`{}` is in the crate `{}`, which will not be documented", + path_str, + cx.tcx.crate_name(destination_id.krate) + )); + return; + } + + // NOTE: `href_inner` is buggy and thinks that primitive associated items can be unreachable + // assert_ne!(why, HrefError::Private, "{:?}", destination_id); + + if let Some(attr) = + cx.tcx.get_attrs(destination_id).lists(sym::doc).get_word_attr(sym::hidden) + { + diag.span_label(attr.span(), &format!("`{}` is marked as `#[doc(hidden)]`", path_str)); + return; + } + + let mut current = destination_id; + while let Some(parent) = cx.tcx.parent(current) { + if let Some(attr) = cx.tcx.get_attrs(parent).lists(sym::doc).get_word_attr(sym::hidden) { + let name = cx.tcx.item_name(parent); + let (_, description) = cx.tcx.article_and_description(parent); + let span = cx.tcx.def_span(parent); + diag.span_label( + span, + &format!( + "`{}` is in the {} `{}`, which is marked as `#[doc(hidden)]`", + path_str, description, name + ), + ); + diag.span_label(attr.span(), &format!("`{}` is hidden here", name)); + return; + } + current = parent; + } + + diag.note(&format!( + "`{}` may be in a private module with all re-exports marked as `#[doc(no_inline)]`", + path_str + )); + }); +} + /// Report a link from a public item to a private one. fn privacy_error(cx: &DocContext<'_>, diag_info: &DiagnosticInfo<'_>, path_str: &str) { let sym; diff --git a/src/librustdoc/passes/mod.rs b/src/librustdoc/passes/mod.rs index 5db2d0747c83b..03346d2e66a29 100644 --- a/src/librustdoc/passes/mod.rs +++ b/src/librustdoc/passes/mod.rs @@ -94,26 +94,34 @@ crate const PASSES: &[Pass] = &[ ]; /// The list of passes run by default. -crate const DEFAULT_PASSES: &[ConditionalPass] = &[ - ConditionalPass::always(COLLECT_TRAIT_IMPLS), - ConditionalPass::always(UNINDENT_COMMENTS), - ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), - ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), - ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), - ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), - ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), - ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX), - ConditionalPass::always(CHECK_INVALID_HTML_TAGS), - ConditionalPass::always(PROPAGATE_DOC_CFG), - ConditionalPass::always(CHECK_BARE_URLS), -]; +crate const DEFAULT_PASSES: (&[ConditionalPass], &[ConditionalPass]) = ( + &[ + ConditionalPass::always(COLLECT_TRAIT_IMPLS), + ConditionalPass::always(UNINDENT_COMMENTS), + ConditionalPass::always(CHECK_DOC_TEST_VISIBILITY), + ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), + ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), + ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate), + ConditionalPass::always(PROPAGATE_DOC_CFG), + ], + // populate cache + &[ + ConditionalPass::always(COLLECT_INTRA_DOC_LINKS), + ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX), + ConditionalPass::always(CHECK_INVALID_HTML_TAGS), + ConditionalPass::always(CHECK_BARE_URLS), + ], +); /// The list of default passes run when `--doc-coverage` is passed to rustdoc. -crate const COVERAGE_PASSES: &[ConditionalPass] = &[ - ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), - ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), - ConditionalPass::always(CALCULATE_DOC_COVERAGE), -]; +crate const COVERAGE_PASSES: (&[ConditionalPass], &[ConditionalPass]) = ( + &[ + ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden), + ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate), + ConditionalPass::always(CALCULATE_DOC_COVERAGE), + ], + &[], +); impl ConditionalPass { crate const fn always(pass: Pass) -> Self { @@ -126,7 +134,7 @@ impl ConditionalPass { } /// Returns the given default set of passes. -crate fn defaults(show_coverage: bool) -> &'static [ConditionalPass] { +crate fn defaults(show_coverage: bool) -> (&'static [ConditionalPass], &'static [ConditionalPass]) { if show_coverage { COVERAGE_PASSES } else { DEFAULT_PASSES } } diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b16cab1c646f1..44cee081be9c3 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -55,7 +55,7 @@ crate trait DocVisitor: Sized { } fn visit_mod(&mut self, m: &Module) { - m.items.iter().for_each(|i| self.visit_item(i)) + m.items.iter().for_each(|i| self.visit_item(i)); } fn visit_crate(&mut self, c: &Crate) { diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index e793ee75fd2e0..61a39098a599d 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -161,6 +161,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { self.inside_public_path &= self.cx.tcx.visibility(def_id).is_public(); for &i in m.item_ids { let item = self.cx.tcx.hir().item(i); + debug!("{:?}", item); self.visit_item(item, None, &mut om); } self.inside_public_path = orig_inside_public_path; @@ -368,7 +369,9 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { hir::ItemKind::Impl(ref impl_) => { // Don't duplicate impls when inlining or if it's implementing a trait, we'll pick // them up regardless of where they're located. + debug!("handle impl block"); if !self.inlining && impl_.of_trait.is_none() { + debug!("propagate impl block"); om.items.push((item, None)); } } diff --git a/src/test/rustdoc-ui/intra-doc/extern-crate-load.rs b/src/test/rustdoc-ui/intra-doc/extern-crate-load.rs index 438c56aa516a9..eca295a2ee24e 100644 --- a/src/test/rustdoc-ui/intra-doc/extern-crate-load.rs +++ b/src/test/rustdoc-ui/intra-doc/extern-crate-load.rs @@ -4,6 +4,7 @@ // aux-crate:dep3=dep3.rs // aux-crate:dep4=dep4.rs #![deny(rustdoc::broken_intra_doc_links)] +#![allow(rustdoc::private_intra_doc_links)] pub trait Trait { /// [dep1] diff --git a/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.rs b/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.rs index 3cfac942ca85e..b929cb0d3a185 100644 --- a/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.rs +++ b/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.rs @@ -1,6 +1,8 @@ //! [pointer::add] //~^ ERROR: experimental +//~| WARNING will not have documentation generated //! [pointer::wrapping_add] //~^ ERROR: experimental +//~| WARNING will not have documentation generated //! [pointer] // This is explicitly allowed //! [reference] // This is explicitly allowed diff --git a/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.stderr b/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.stderr index 2c946ed48db17..754a69088d85a 100644 --- a/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.stderr +++ b/src/test/rustdoc-ui/intra-doc/feature-gate-intra-doc-pointers.stderr @@ -1,3 +1,12 @@ +warning: documentation for `feature_gate_intra_doc_pointers` links to item `pointer::add` which will not have documentation generated + --> $DIR/feature-gate-intra-doc-pointers.rs:1:6 + | +LL | //! [pointer::add] + | ^^^^^^^^^^^^ this item is will not be documented + | + = note: `#[warn(rustdoc::private_intra_doc_links)]` on by default + = note: `pointer::add` may be in a private module with all re-exports marked as `#[doc(no_inline)]` + error[E0658]: linking to associated items of raw pointers is experimental --> $DIR/feature-gate-intra-doc-pointers.rs:1:6 | @@ -8,8 +17,16 @@ LL | //! [pointer::add] = help: add `#![feature(intra_doc_pointers)]` to the crate attributes to enable = note: rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does +warning: documentation for `feature_gate_intra_doc_pointers` links to item `pointer::wrapping_add` which will not have documentation generated + --> $DIR/feature-gate-intra-doc-pointers.rs:4:6 + | +LL | //! [pointer::wrapping_add] + | ^^^^^^^^^^^^^^^^^^^^^ this item is will not be documented + | + = note: `pointer::wrapping_add` may be in a private module with all re-exports marked as `#[doc(no_inline)]` + error[E0658]: linking to associated items of raw pointers is experimental - --> $DIR/feature-gate-intra-doc-pointers.rs:3:6 + --> $DIR/feature-gate-intra-doc-pointers.rs:4:6 | LL | //! [pointer::wrapping_add] | ^^^^^^^^^^^^^^^^^^^^^ @@ -18,6 +35,6 @@ LL | //! [pointer::wrapping_add] = help: add `#![feature(intra_doc_pointers)]` to the crate attributes to enable = note: rustdoc does not allow disambiguating between `*const` and `*mut`, and pointers are unstable until it does -error: aborting due to 2 previous errors +error: aborting due to 2 previous errors; 2 warnings emitted For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/rustdoc-ui/intra-doc/warn-undocumented.rs b/src/test/rustdoc-ui/intra-doc/warn-undocumented.rs new file mode 100644 index 0000000000000..ea2627b411418 --- /dev/null +++ b/src/test/rustdoc-ui/intra-doc/warn-undocumented.rs @@ -0,0 +1,42 @@ +// aux-build:dep1.rs + +#![deny(rustdoc::private_intra_doc_links)] +//~^ NOTE defined here + +//! Link to [dep1] +//~^ ERROR will not have documentation generated +//~| NOTE will not be documented +//~| crate `dep1`, which will not be documented + +//! Link to [S] +//~^ ERROR will not have documentation generated +//~| NOTE will not be documented +//~| NOTE may be in a private module + +//! Link to [T] +//~^ ERROR will not have documentation generated +//~| NOTE will not be documented + +//! Link to [secret::U] +//~^ ERROR will not have documentation generated +//~| NOTE will not be documented + +extern crate dep1; + +#[doc(no_inline)] +pub use inner::S; + +mod inner { + pub struct S; +} + +#[doc(hidden)] +//~^ NOTE doc(hidden) +pub struct T; + +#[doc(hidden)] +//~^ NOTE `secret` is hidden +pub mod secret { +//~^ NOTE doc(hidden) + pub struct U; +} \ No newline at end of file diff --git a/src/test/rustdoc-ui/intra-doc/warn-undocumented.stderr b/src/test/rustdoc-ui/intra-doc/warn-undocumented.stderr new file mode 100644 index 0000000000000..2bc4f220e70b8 --- /dev/null +++ b/src/test/rustdoc-ui/intra-doc/warn-undocumented.stderr @@ -0,0 +1,47 @@ +error: documentation for `warn_undocumented` links to item `dep1` which will not have documentation generated + --> $DIR/warn-undocumented.rs:6:14 + | +LL | //! Link to [dep1] + | ^^^^ this item is will not be documented + | +note: the lint level is defined here + --> $DIR/warn-undocumented.rs:3:9 + | +LL | #![deny(rustdoc::private_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `dep1` is in the crate `dep1`, which will not be documented + +error: documentation for `warn_undocumented` links to item `S` which will not have documentation generated + --> $DIR/warn-undocumented.rs:11:14 + | +LL | //! Link to [S] + | ^ this item is will not be documented + | + = note: `S` may be in a private module with all re-exports marked as `#[doc(no_inline)]` + +error: documentation for `warn_undocumented` links to item `T` which will not have documentation generated + --> $DIR/warn-undocumented.rs:16:14 + | +LL | //! Link to [T] + | ^ this item is will not be documented +... +LL | #[doc(hidden)] + | ------ `T` is marked as `#[doc(hidden)]` + +error: documentation for `warn_undocumented` links to item `secret::U` which will not have documentation generated + --> $DIR/warn-undocumented.rs:20:14 + | +LL | //! Link to [secret::U] + | ^^^^^^^^^ this item is will not be documented +... +LL | #[doc(hidden)] + | ------ `secret` is hidden here +LL | +LL | / pub mod secret { +LL | | +LL | | pub struct U; +LL | | } + | |_- `secret::U` is in the module `secret`, which is marked as `#[doc(hidden)]` + +error: aborting due to 4 previous errors + diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 6b27d1ecbf550..77c265ca6af60 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -1490,6 +1490,7 @@ impl<'test> TestCx<'test> { .arg(out_dir) .arg("--deny") .arg("warnings") + .arg("-Arustdoc::private-intra-doc-links") .arg(&self.testpaths.file) .args(&self.props.compile_flags);