Skip to content

Allow #![doc(test(attr(..)))] everywhere #140560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
@@ -1266,13 +1266,17 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
true
}

/// Checks that `doc(test(...))` attribute contains only valid attributes. Returns `true` if
/// valid.
fn check_test_attr(&self, meta: &MetaItemInner, hir_id: HirId) {
/// Checks that `doc(test(...))` attribute contains only valid attributes and are at the right place.
fn check_test_attr(&self, attr: &Attribute, meta: &MetaItemInner, hir_id: HirId) {
if let Some(metas) = meta.meta_item_list() {
for i_meta in metas {
match (i_meta.name(), i_meta.meta_item()) {
(Some(sym::attr | sym::no_crate_inject), _) => {}
(Some(sym::attr), _) => {
// Allowed everywhere like `#[doc]`
}
(Some(sym::no_crate_inject), _) => {
self.check_attr_crate_level(attr, meta, hir_id);
}
(_, Some(m)) => {
self.tcx.emit_node_span_lint(
INVALID_DOC_ATTRIBUTES,
@@ -1359,9 +1363,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}

Some(sym::test) => {
if self.check_attr_crate_level(attr, meta, hir_id) {
self.check_test_attr(meta, hir_id);
}
self.check_test_attr(attr, meta, hir_id);
}

Some(
32 changes: 23 additions & 9 deletions src/doc/rustdoc/src/write-documentation/the-doc-attribute.md
Original file line number Diff line number Diff line change
@@ -141,15 +141,6 @@ But if you include this:

it will not.

### `test(attr(...))`

This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For
example, if you want your doctests to fail if they have dead code, you could add this:

```rust,no_run
#![doc(test(attr(deny(dead_code))))]
```

## At the item level

These forms of the `#[doc]` attribute are used on individual items, to control how
@@ -281,3 +272,26 @@ To get around this limitation, we just add `#[doc(alias = "lib_name_do_something
on the `do_something` method and then it's all good!
Users can now look for `lib_name_do_something` in our crate directly and find
`Obj::do_something`.

### `test(attr(...))`

This form of the `doc` attribute allows you to add arbitrary attributes to all your doctests. For
example, if you want your doctests to fail if they have dead code, you could add this:

```rust,no_run
#![doc(test(attr(deny(dead_code))))]

mod my_mod {
#![doc(test(attr(allow(dead_code))))] // but allow `dead_code` for this module
}
```

`test(attr(..))` attributes are appended to the parent module's, they do not replace the current
list of attributes. In the previous example, both attributes would be present:

```rust,no_run
// For every doctest in `my_mod`

#![deny(dead_code)] // from the crate-root
#![allow(dead_code)] // from `my_mod`
```
49 changes: 29 additions & 20 deletions src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ mod runner;
mod rust;

use std::fs::File;
use std::hash::{Hash, Hasher};
use std::io::{self, Write};
use std::path::{Path, PathBuf};
use std::process::{self, Command, Stdio};
@@ -14,7 +15,7 @@ use std::{panic, str};

pub(crate) use make::DocTestBuilder;
pub(crate) use markdown::test as test_markdown;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
use rustc_data_structures::fx::{FxHashMap, FxHasher, FxIndexMap, FxIndexSet};
use rustc_errors::emitter::HumanReadableErrorType;
use rustc_errors::{ColorConfig, DiagCtxtHandle};
use rustc_hir as hir;
@@ -45,8 +46,6 @@ pub(crate) struct GlobalTestOptions {
/// Whether inserting extra indent spaces in code block,
/// default is `false`, only `true` for generating code link of Rust playground
pub(crate) insert_indent_space: bool,
/// Additional crate-level attributes to add to doctests.
pub(crate) attrs: Vec<String>,
/// Path to file containing arguments for the invocation of rustc.
pub(crate) args_file: PathBuf,
}
@@ -283,7 +282,7 @@ pub(crate) fn run_tests(
rustdoc_options: &Arc<RustdocOptions>,
unused_extern_reports: &Arc<Mutex<Vec<UnusedExterns>>>,
mut standalone_tests: Vec<test::TestDescAndFn>,
mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,
// We pass this argument so we can drop it manually before using `exit`.
mut temp_dir: Option<TempDir>,
) {
@@ -298,7 +297,7 @@ pub(crate) fn run_tests(
let mut ran_edition_tests = 0;
let target_str = rustdoc_options.target.to_string();

for (edition, mut doctests) in mergeable_tests {
for (MergeableTestKey { edition, global_crate_attrs_hash }, mut doctests) in mergeable_tests {
if doctests.is_empty() {
continue;
}
@@ -308,8 +307,8 @@ pub(crate) fn run_tests(

let rustdoc_test_options = IndividualTestOptions::new(
rustdoc_options,
&Some(format!("merged_doctest_{edition}")),
PathBuf::from(format!("doctest_{edition}.rs")),
&Some(format!("merged_doctest_{edition}_{global_crate_attrs_hash}")),
PathBuf::from(format!("doctest_{edition}_{global_crate_attrs_hash}.rs")),
);

for (doctest, scraped_test) in &doctests {
@@ -371,12 +370,9 @@ fn scrape_test_config(
attrs: &[hir::Attribute],
args_file: PathBuf,
) -> GlobalTestOptions {
use rustc_ast_pretty::pprust;

let mut opts = GlobalTestOptions {
crate_name,
no_crate_inject: false,
attrs: Vec::new(),
insert_indent_space: false,
args_file,
};
@@ -393,13 +389,7 @@ fn scrape_test_config(
if attr.has_name(sym::no_crate_inject) {
opts.no_crate_inject = true;
}
if attr.has_name(sym::attr)
&& let Some(l) = attr.meta_item_list()
{
for item in l {
opts.attrs.push(pprust::meta_list_item_to_string(item));
}
}
// NOTE: `test(attr(..))` is handled when discovering the individual tests
}

opts
@@ -847,6 +837,7 @@ pub(crate) struct ScrapedDocTest {
langstr: LangString,
text: String,
name: String,
global_crate_attrs: Vec<String>,
}

impl ScrapedDocTest {
@@ -856,6 +847,7 @@ impl ScrapedDocTest {
logical_path: Vec<String>,
langstr: LangString,
text: String,
global_crate_attrs: Vec<String>,
) -> Self {
let mut item_path = logical_path.join("::");
item_path.retain(|c| c != ' ');
@@ -865,7 +857,7 @@ impl ScrapedDocTest {
let name =
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());

Self { filename, line, langstr, text, name }
Self { filename, line, langstr, text, name, global_crate_attrs }
}
fn edition(&self, opts: &RustdocOptions) -> Edition {
self.langstr.edition.unwrap_or(opts.edition)
@@ -894,9 +886,15 @@ pub(crate) trait DocTestVisitor {
fn visit_header(&mut self, _name: &str, _level: u32) {}
}

#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) struct MergeableTestKey {
edition: Edition,
global_crate_attrs_hash: u64,
}

struct CreateRunnableDocTests {
standalone_tests: Vec<test::TestDescAndFn>,
mergeable_tests: FxIndexMap<Edition, Vec<(DocTestBuilder, ScrapedDocTest)>>,
mergeable_tests: FxIndexMap<MergeableTestKey, Vec<(DocTestBuilder, ScrapedDocTest)>>,

rustdoc_options: Arc<RustdocOptions>,
opts: GlobalTestOptions,
@@ -949,6 +947,7 @@ impl CreateRunnableDocTests {
&scraped_test.text,
Some(&self.opts.crate_name),
edition,
scraped_test.global_crate_attrs.clone(),
self.can_merge_doctests,
Some(test_id),
Some(&scraped_test.langstr),
@@ -963,7 +962,17 @@ impl CreateRunnableDocTests {
let test_desc = self.generate_test_desc_and_fn(doctest, scraped_test);
self.standalone_tests.push(test_desc);
} else {
self.mergeable_tests.entry(edition).or_default().push((doctest, scraped_test));
self.mergeable_tests
.entry(MergeableTestKey {
edition,
global_crate_attrs_hash: {
let mut hasher = FxHasher::default();
scraped_test.global_crate_attrs.hash(&mut hasher);
hasher.finish()
},
})
.or_default()
.push((doctest, scraped_test));
}
}

4 changes: 3 additions & 1 deletion src/librustdoc/doctest/extracted.rs
Original file line number Diff line number Diff line change
@@ -35,12 +35,14 @@ impl ExtractedDocTests {
) {
let edition = scraped_test.edition(options);

let ScrapedDocTest { filename, line, langstr, text, name } = scraped_test;
let ScrapedDocTest { filename, line, langstr, text, name, global_crate_attrs } =
scraped_test;

let doctest = DocTestBuilder::new(
&text,
Some(&opts.crate_name),
edition,
global_crate_attrs,
false,
None,
Some(&langstr),
13 changes: 10 additions & 3 deletions src/librustdoc/doctest/make.rs
Original file line number Diff line number Diff line change
@@ -41,6 +41,7 @@ pub(crate) struct DocTestBuilder {
pub(crate) supports_color: bool,
pub(crate) already_has_extern_crate: bool,
pub(crate) has_main_fn: bool,
pub(crate) global_crate_attrs: Vec<String>,
pub(crate) crate_attrs: String,
/// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
/// put into `crate_attrs`.
@@ -57,6 +58,7 @@ impl DocTestBuilder {
source: &str,
crate_name: Option<&str>,
edition: Edition,
global_crate_attrs: Vec<String>,
can_merge_doctests: bool,
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
test_id: Option<String>,
@@ -88,6 +90,7 @@ impl DocTestBuilder {
// If the AST returned an error, we don't want this doctest to be merged with the
// others.
return Self::invalid(
Vec::new(),
String::new(),
String::new(),
String::new(),
@@ -110,6 +113,7 @@ impl DocTestBuilder {
Self {
supports_color,
has_main_fn,
global_crate_attrs,
crate_attrs,
maybe_crate_attrs,
crates,
@@ -122,6 +126,7 @@ impl DocTestBuilder {
}

fn invalid(
global_crate_attrs: Vec<String>,
crate_attrs: String,
maybe_crate_attrs: String,
crates: String,
@@ -131,6 +136,7 @@ impl DocTestBuilder {
Self {
supports_color: false,
has_main_fn: false,
global_crate_attrs,
crate_attrs,
maybe_crate_attrs,
crates,
@@ -160,7 +166,8 @@ impl DocTestBuilder {
let mut line_offset = 0;
let mut prog = String::new();
let everything_else = self.everything_else.trim();
if opts.attrs.is_empty() {

if self.global_crate_attrs.is_empty() {
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
// lints that are commonly triggered in doctests. The crate-level test attributes are
// commonly used to make tests fail in case they trigger warnings, so having this there in
@@ -169,8 +176,8 @@ impl DocTestBuilder {
line_offset += 1;
}

// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
for attr in &opts.attrs {
// Next, any attributes that came from #![doc(test(attr(...)))].
for attr in &self.global_crate_attrs {
prog.push_str(&format!("#![{attr}]\n"));
line_offset += 1;
}
10 changes: 8 additions & 2 deletions src/librustdoc/doctest/markdown.rs
Original file line number Diff line number Diff line change
@@ -24,7 +24,14 @@ impl DocTestVisitor for MdCollector {
let filename = self.filename.clone();
// First line of Markdown is line 1.
let line = 1 + rel_line.offset();
self.tests.push(ScrapedDocTest::new(filename, line, self.cur_path.clone(), config, test));
self.tests.push(ScrapedDocTest::new(
filename,
line,
self.cur_path.clone(),
config,
test,
Vec::new(),
));
}

fn visit_header(&mut self, name: &str, level: u32) {
@@ -89,7 +96,6 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
crate_name,
no_crate_inject: true,
insert_indent_space: false,
attrs: vec![],
args_file,
};

11 changes: 8 additions & 3 deletions src/librustdoc/doctest/runner.rs
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ use crate::html::markdown::{Ignore, LangString};
/// Convenient type to merge compatible doctests into one.
pub(crate) struct DocTestRunner {
crate_attrs: FxIndexSet<String>,
global_crate_attrs: FxIndexSet<String>,
ids: String,
output: String,
output_merged_tests: String,
@@ -23,6 +24,7 @@ impl DocTestRunner {
pub(crate) fn new() -> Self {
Self {
crate_attrs: FxIndexSet::default(),
global_crate_attrs: FxIndexSet::default(),
ids: String::new(),
output: String::new(),
output_merged_tests: String::new(),
@@ -46,6 +48,9 @@ impl DocTestRunner {
for line in doctest.crate_attrs.split('\n') {
self.crate_attrs.insert(line.to_string());
}
for line in &doctest.global_crate_attrs {
self.global_crate_attrs.insert(line.to_string());
}
}
self.ids.push_str(&format!(
"tests.push({}::TEST);\n",
@@ -85,16 +90,16 @@ impl DocTestRunner {
code_prefix.push('\n');
}

if opts.attrs.is_empty() {
if self.global_crate_attrs.is_empty() {
// If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
// lints that are commonly triggered in doctests. The crate-level test attributes are
// commonly used to make tests fail in case they trigger warnings, so having this there in
// that case may cause some tests to pass when they shouldn't have.
code_prefix.push_str("#![allow(unused)]\n");
}

// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
for attr in &opts.attrs {
// Next, any attributes that came from #![doc(test(attr(...)))].
for attr in &self.global_crate_attrs {
code_prefix.push_str(&format!("#![{attr}]\n"));
}

29 changes: 28 additions & 1 deletion src/librustdoc/doctest/rust.rs
Original file line number Diff line number Diff line change
@@ -3,14 +3,15 @@
use std::env;
use std::sync::Arc;

use rustc_ast_pretty::pprust;
use rustc_data_structures::fx::FxHashSet;
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
use rustc_hir::{self as hir, CRATE_HIR_ID, intravisit};
use rustc_middle::hir::nested_filter;
use rustc_middle::ty::TyCtxt;
use rustc_resolve::rustdoc::span_of_fragments;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span};
use rustc_span::{BytePos, DUMMY_SP, FileName, Pos, Span, sym};

use super::{DocTestVisitor, ScrapedDocTest};
use crate::clean::{Attributes, extract_cfg_from_attrs};
@@ -21,6 +22,7 @@ struct RustCollector {
tests: Vec<ScrapedDocTest>,
cur_path: Vec<String>,
position: Span,
global_crate_attrs: Vec<String>,
}

impl RustCollector {
@@ -54,6 +56,7 @@ impl DocTestVisitor for RustCollector {
self.cur_path.clone(),
config,
test,
self.global_crate_attrs.clone(),
));
}

@@ -73,6 +76,7 @@ impl<'tcx> HirCollector<'tcx> {
cur_path: vec![],
position: DUMMY_SP,
tests: vec![],
global_crate_attrs: Vec::new(),
};
Self { codes, tcx, collector }
}
@@ -102,6 +106,26 @@ impl HirCollector<'_> {
return;
}

// Try collecting `#[doc(test(attr(...)))]`
let old_global_crate_attrs_len = self.collector.global_crate_attrs.len();
for doc_test_attrs in ast_attrs
.iter()
.filter(|a| a.has_name(sym::doc))
.flat_map(|a| a.meta_item_list().unwrap_or_default())
.filter(|a| a.has_name(sym::test))
{
let Some(doc_test_attrs) = doc_test_attrs.meta_item_list() else { continue };
for attr in doc_test_attrs
.iter()
.filter(|a| a.has_name(sym::attr))
.flat_map(|a| a.meta_item_list().unwrap_or_default())
.map(|i| pprust::meta_list_item_to_string(i))
{
// Add the additional attributes to the global_crate_attrs vector
self.collector.global_crate_attrs.push(attr);
}
}

let mut has_name = false;
if let Some(name) = name {
self.collector.cur_path.push(name);
@@ -136,6 +160,9 @@ impl HirCollector<'_> {

nested(self);

// Restore global_crate_attrs to it's previous size/content
self.collector.global_crate_attrs.truncate(old_global_crate_attrs_len);

if has_name {
self.collector.cur_path.pop();
}
66 changes: 38 additions & 28 deletions src/librustdoc/doctest/tests.rs
Original file line number Diff line number Diff line change
@@ -9,12 +9,14 @@ fn make_test(
crate_name: Option<&str>,
dont_insert_main: bool,
opts: &GlobalTestOptions,
global_crate_attrs: Vec<&str>,
test_id: Option<&str>,
) -> (String, usize) {
let doctest = DocTestBuilder::new(
test_code,
crate_name,
DEFAULT_EDITION,
global_crate_attrs.into_iter().map(|a| a.to_string()).collect(),
false,
test_id.map(|s| s.to_string()),
None,
@@ -30,7 +32,6 @@ fn default_global_opts(crate_name: impl Into<String>) -> GlobalTestOptions {
crate_name: crate_name.into(),
no_crate_inject: false,
insert_indent_space: false,
attrs: vec![],
args_file: PathBuf::new(),
}
}
@@ -45,7 +46,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -60,7 +61,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -79,7 +80,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 3));
}

@@ -96,7 +97,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -114,7 +115,7 @@ use std::*;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("std"), false, &opts, None);
let (output, len) = make_test(input, Some("std"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -133,7 +134,7 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -150,16 +151,15 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

#[test]
fn make_test_opts_attrs() {
// If you supplied some doctest attributes with `#![doc(test(attr(...)))]`, it will use
// those instead of the stock `#![allow(unused)]`.
let mut opts = default_global_opts("asdf");
opts.attrs.push("feature(sick_rad)".to_string());
let opts = default_global_opts("asdf");
let input = "use asdf::qwop;
assert_eq!(2+2, 4);";
let expected = "#![feature(sick_rad)]
@@ -170,11 +170,10 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) =
make_test(input, Some("asdf"), false, &opts, vec!["feature(sick_rad)"], None);
assert_eq!((output, len), (expected, 3));

// Adding more will also bump the returned line offset.
opts.attrs.push("feature(hella_dope)".to_string());
let expected = "#![feature(sick_rad)]
#![feature(hella_dope)]
#[allow(unused_extern_crates)]
@@ -184,7 +183,18 @@ use asdf::qwop;
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(
input,
Some("asdf"),
false,
&opts,
vec![
"feature(sick_rad)",
// Adding more will also bump the returned line offset.
"feature(hella_dope)",
],
None,
);
assert_eq!((output, len), (expected, 4));
}

@@ -202,7 +212,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -218,7 +228,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 1));
}

@@ -234,7 +244,7 @@ fn main() {
assert_eq!(2+2, 4);
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -248,7 +258,7 @@ assert_eq!(2+2, 4);";
//Ceci n'est pas une `fn main`
assert_eq!(2+2, 4);"
.to_string();
let (output, len) = make_test(input, None, true, &opts, None);
let (output, len) = make_test(input, None, true, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 1));
}

@@ -266,7 +276,7 @@ assert_eq!(2+2, 4);
}"
.to_string();

let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -286,7 +296,7 @@ assert_eq!(asdf::foo, 4);
}"
.to_string();

let (output, len) = make_test(input, Some("asdf"), false, &opts, None);
let (output, len) = make_test(input, Some("asdf"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 3));
}

@@ -304,7 +314,7 @@ test_wrapper! {
}"
.to_string();

let (output, len) = make_test(input, Some("my_crate"), false, &opts, None);
let (output, len) = make_test(input, Some("my_crate"), false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 1));
}

@@ -324,7 +334,7 @@ io::stdin().read_line(&mut input)?;
Ok::<(), io:Error>(())
} _inner().unwrap() }"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -338,7 +348,7 @@ fn main() { #[allow(non_snake_case)] fn _doctest_main__some_unique_name() {
assert_eq!(2+2, 4);
} _doctest_main__some_unique_name() }"
.to_string();
let (output, len) = make_test(input, None, false, &opts, Some("_some_unique_name"));
let (output, len) = make_test(input, None, false, &opts, Vec::new(), Some("_some_unique_name"));
assert_eq!((output, len), (expected, 2));
}

@@ -357,7 +367,7 @@ fn main() {
eprintln!(\"hello anan\");
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}

@@ -377,7 +387,7 @@ fn main() {
eprintln!(\"hello anan\");
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 1));
}

@@ -402,7 +412,7 @@ fn main() {
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));

// And same, if there is a `main` function provided by the user, we ensure that it's
@@ -422,7 +432,7 @@ fn main() {}";
fn main() {}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 1));
}

@@ -450,6 +460,6 @@ pub mod outer_module {
}
}"
.to_string();
let (output, len) = make_test(input, None, false, &opts, None);
let (output, len) = make_test(input, None, false, &opts, Vec::new(), None);
assert_eq!((output, len), (expected, 2));
}
4 changes: 2 additions & 2 deletions src/librustdoc/html/markdown.rs
Original file line number Diff line number Diff line change
@@ -300,10 +300,10 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
crate_name: krate.map(String::from).unwrap_or_default(),
no_crate_inject: false,
insert_indent_space: true,
attrs: vec![],
args_file: PathBuf::new(),
};
let doctest = doctest::DocTestBuilder::new(&test, krate, edition, false, None, None);
let doctest =
doctest::DocTestBuilder::new(&test, krate, edition, Vec::new(), false, None, None);
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };

17 changes: 16 additions & 1 deletion tests/run-make/doctests-keep-binaries-2024/rmake.rs
Original file line number Diff line number Diff line change
@@ -16,7 +16,22 @@ fn setup_test_env<F: FnOnce(&Path, &Path)>(callback: F) {
}

fn check_generated_binaries() {
run("doctests/merged_doctest_2024/rust_out");
let mut found_merged_doctest = false;
rfs::read_dir_entries("doctests/", |path| {
if path
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with("merged_doctest_2024"))
{
found_merged_doctest = true;
let rust_out = path.join("rust_out");
let rust_out = rust_out.to_string_lossy();
run(&*rust_out);
}
});
if !found_merged_doctest {
panic!("no directory starting with `merged_doctest_2024` found under `doctests/`");
}
}

fn main() {
116 changes: 116 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Same test as dead-code-module but with 2 doc(test(attr())) at different levels.

//@ edition: 2024
//@ compile-flags:--test --test-args=--test-threads=1
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ failure-status: 101

#![doc(test(attr(deny(warnings))))]

#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait OnlyWarning { fn no_deny_warnings(); }
/// ```
static S: u32 = 5;

#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// let unused_error = 5;
///
/// fn dead_code_but_no_error() {}
/// ```
const C: u32 = 5;

#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait OnlyWarningAtA { fn no_deny_warnings(); }
/// ```
struct A {
#[doc(test(attr(deny(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait DeadCodeInField {}
/// ```
field: u32
}

#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait OnlyWarningAtU { fn no_deny_warnings(); }
/// ```
union U {
#[doc(test(attr(deny(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait DeadCodeInUnionField {}
/// ```
field: u32,
/// Example
///
/// ```rust,no_run
/// trait NotDeadCodeInUnionField {}
/// ```
field2: u64,
}

#[doc(test(attr(deny(dead_code))))]
/// Example
///
/// ```rust,no_run
/// let not_dead_code_but_unused = 5;
/// ```
enum Enum {
#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait NotDeadCodeInVariant {}
///
/// fn main() { let unused_in_variant = 5; }
/// ```
Variant1,
}

#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait OnlyWarningAtImplA { fn no_deny_warnings(); }
/// ```
impl A {
/// Example
///
/// ```rust,no_run
/// trait NotDeadCodeInImplMethod {}
/// ```
fn method() {}
}

#[doc(test(attr(deny(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait StillDeadCodeAtMyTrait { }
/// ```
trait MyTrait {
#[doc(test(attr(allow(dead_code))))]
/// Example
///
/// ```rust,no_run
/// trait NotDeadCodeAtImplFn {}
///
/// fn main() { let unused_in_impl = 5; }
/// ```
fn my_trait_fn();
}
146 changes: 146 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-items.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@

running 13 tests
test $DIR/dead-code-items.rs - A (line 32) - compile ... ok
test $DIR/dead-code-items.rs - A (line 88) - compile ... ok
test $DIR/dead-code-items.rs - A::field (line 39) - compile ... FAILED
test $DIR/dead-code-items.rs - A::method (line 94) - compile ... ok
test $DIR/dead-code-items.rs - C (line 22) - compile ... FAILED
test $DIR/dead-code-items.rs - Enum (line 70) - compile ... FAILED
test $DIR/dead-code-items.rs - Enum::Variant1 (line 77) - compile ... FAILED
test $DIR/dead-code-items.rs - MyTrait (line 103) - compile ... FAILED
test $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) - compile ... FAILED
test $DIR/dead-code-items.rs - S (line 14) - compile ... ok
test $DIR/dead-code-items.rs - U (line 48) - compile ... ok
test $DIR/dead-code-items.rs - U::field (line 55) - compile ... FAILED
test $DIR/dead-code-items.rs - U::field2 (line 61) - compile ... ok

failures:

---- $DIR/dead-code-items.rs - A::field (line 39) stdout ----
error: trait `DeadCodeInField` is never used
--> $DIR/dead-code-items.rs:40:7
|
LL | trait DeadCodeInField {}
| ^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:38:9
|
LL | #![deny(dead_code)]
| ^^^^^^^^^

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - C (line 22) stdout ----
error: unused variable: `unused_error`
--> $DIR/dead-code-items.rs:23:5
|
LL | let unused_error = 5;
| ^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_error`
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:20:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - Enum (line 70) stdout ----
error: unused variable: `not_dead_code_but_unused`
--> $DIR/dead-code-items.rs:71:5
|
LL | let not_dead_code_but_unused = 5;
| ^^^^^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_not_dead_code_but_unused`
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:68:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - Enum::Variant1 (line 77) stdout ----
error: unused variable: `unused_in_variant`
--> $DIR/dead-code-items.rs:80:17
|
LL | fn main() { let unused_in_variant = 5; }
| ^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_variant`
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:75:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - MyTrait (line 103) stdout ----
error: trait `StillDeadCodeAtMyTrait` is never used
--> $DIR/dead-code-items.rs:104:7
|
LL | trait StillDeadCodeAtMyTrait { }
| ^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:102:9
|
LL | #![deny(dead_code)]
| ^^^^^^^^^

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110) stdout ----
error: unused variable: `unused_in_impl`
--> $DIR/dead-code-items.rs:113:17
|
LL | fn main() { let unused_in_impl = 5; }
| ^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_unused_in_impl`
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:108:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(unused_variables)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.
---- $DIR/dead-code-items.rs - U::field (line 55) stdout ----
error: trait `DeadCodeInUnionField` is never used
--> $DIR/dead-code-items.rs:56:7
|
LL | trait DeadCodeInUnionField {}
| ^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> $DIR/dead-code-items.rs:54:9
|
LL | #![deny(dead_code)]
| ^^^^^^^^^

error: aborting due to 1 previous error

Couldn't compile the test.

failures:
$DIR/dead-code-items.rs - A::field (line 39)
$DIR/dead-code-items.rs - C (line 22)
$DIR/dead-code-items.rs - Enum (line 70)
$DIR/dead-code-items.rs - Enum::Variant1 (line 77)
$DIR/dead-code-items.rs - MyTrait (line 103)
$DIR/dead-code-items.rs - MyTrait::my_trait_fn (line 110)
$DIR/dead-code-items.rs - U::field (line 55)

test result: FAILED. 6 passed; 7 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

27 changes: 27 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-module-2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Same test as dead-code-module but with 2 doc(test(attr())) at different levels.

//@ edition: 2024
//@ compile-flags:--test
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ failure-status: 101

#![doc(test(attr(allow(unused_variables))))]

mod my_mod {
#![doc(test(attr(deny(warnings))))]

/// Example
///
/// ```rust,no_run
/// trait T { fn f(); }
/// ```
pub fn f() {}
}

/// Example
///
/// ```rust,no_run
/// trait OnlyWarning { fn no_deny_warnings(); }
/// ```
pub fn g() {}
35 changes: 35 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-module-2.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@

running 1 test
test $DIR/dead-code-module-2.rs - g (line 24) - compile ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME


running 1 test
test $DIR/dead-code-module-2.rs - my_mod::f (line 16) - compile ... FAILED

failures:

---- $DIR/dead-code-module-2.rs - my_mod::f (line 16) stdout ----
error: trait `T` is never used
--> $DIR/dead-code-module-2.rs:17:7
|
LL | trait T { fn f(); }
| ^
|
note: the lint level is defined here
--> $DIR/dead-code-module-2.rs:15:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(dead_code)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.

failures:
$DIR/dead-code-module-2.rs - my_mod::f (line 16)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

18 changes: 18 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Same test as dead-code but inside a module.

//@ edition: 2024
//@ compile-flags:--test
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ failure-status: 101

mod my_mod {
#![doc(test(attr(allow(unused_variables), deny(warnings))))]

/// Example
///
/// ```rust,no_run
/// trait T { fn f(); }
/// ```
pub fn f() {}
}
29 changes: 29 additions & 0 deletions tests/rustdoc-ui/doctest/dead-code-module.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

running 1 test
test $DIR/dead-code-module.rs - my_mod::f (line 14) - compile ... FAILED

failures:

---- $DIR/dead-code-module.rs - my_mod::f (line 14) stdout ----
error: trait `T` is never used
--> $DIR/dead-code-module.rs:15:7
|
LL | trait T { fn f(); }
| ^
|
note: the lint level is defined here
--> $DIR/dead-code-module.rs:13:9
|
LL | #![deny(warnings)]
| ^^^^^^^^
= note: `#[deny(dead_code)]` implied by `#[deny(warnings)]`

error: aborting due to 1 previous error

Couldn't compile the test.

failures:
$DIR/dead-code-module.rs - my_mod::f (line 14)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

11 changes: 11 additions & 0 deletions tests/rustdoc-ui/doctest/doc-test-attr-pass-module.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//@ check-pass

#![crate_type = "lib"]
#![deny(invalid_doc_attributes)]
#![doc(test(no_crate_inject))]

mod my_mod {
#![doc(test(attr(deny(warnings))))]

pub fn foo() {}
}
10 changes: 4 additions & 6 deletions tests/ui/future-incompatible-lint-group.rs
Original file line number Diff line number Diff line change
@@ -2,16 +2,14 @@
// lints for changes that are not tied to an edition
#![deny(future_incompatible)]

// Error since this is a `future_incompatible` lint
macro_rules! m { ($i) => {} } //~ ERROR missing fragment specifier
//~| WARN this was previously accepted

trait Tr {
// Warn only since this is not a `future_incompatible` lint
fn f(u8) {} //~ WARN anonymous parameters are deprecated
//~| WARN this is accepted in the current edition
}

pub mod submodule {
// Error since this is a `future_incompatible` lint
#![doc(test(some_test))]
//~^ ERROR this attribute can only be applied at the crate level
}

fn main() {}
40 changes: 31 additions & 9 deletions tests/ui/future-incompatible-lint-group.stderr
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
error: missing fragment specifier
--> $DIR/future-incompatible-lint-group.rs:6:19
|
LL | macro_rules! m { ($i) => {} }
| ^^
|
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #40107 <https://github.com/rust-lang/rust/issues/40107>
note: the lint level is defined here
--> $DIR/future-incompatible-lint-group.rs:3:9
|
LL | #![deny(future_incompatible)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[deny(missing_fragment_specifier)]` implied by `#[deny(future_incompatible)]`

warning: anonymous parameters are deprecated and will be removed in the next edition
--> $DIR/future-incompatible-lint-group.rs:7:10
--> $DIR/future-incompatible-lint-group.rs:11:10
|
LL | fn f(u8) {}
| ^^ help: try naming the parameter or explicitly ignoring it: `_: u8`
@@ -8,14 +23,21 @@ LL | fn f(u8) {}
= note: for more information, see issue #41686 <https://github.com/rust-lang/rust/issues/41686>
= note: `#[warn(anonymous_parameters)]` on by default

error: this attribute can only be applied at the crate level
--> $DIR/future-incompatible-lint-group.rs:13:12
error: aborting due to 1 previous error; 1 warning emitted

Future incompatibility report: Future breakage diagnostic:
error: missing fragment specifier
--> $DIR/future-incompatible-lint-group.rs:6:19
|
LL | #![doc(test(some_test))]
| ^^^^^^^^^^^^^^^
LL | macro_rules! m { ($i) => {} }
| ^^
|
= note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
= note: `#[deny(invalid_doc_attributes)]` on by default

error: aborting due to 1 previous error; 1 warning emitted
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #40107 <https://github.com/rust-lang/rust/issues/40107>
note: the lint level is defined here
--> $DIR/future-incompatible-lint-group.rs:3:9
|
LL | #![deny(future_incompatible)]
| ^^^^^^^^^^^^^^^^^^^
= note: `#[deny(missing_fragment_specifier)]` implied by `#[deny(future_incompatible)]`

7 changes: 7 additions & 0 deletions tests/ui/lint/unused/useless-comment.rs
Original file line number Diff line number Diff line change
@@ -9,8 +9,13 @@ macro_rules! mac {
/// foo //~ ERROR unused doc comment
mac!();

/// a //~ ERROR unused doc comment
#[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment
unsafe extern "C" { }

fn foo() {
/// a //~ ERROR unused doc comment
#[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment
let x = 12;

/// multi-line //~ ERROR unused doc comment
@@ -19,6 +24,7 @@ fn foo() {
match x {
/// c //~ ERROR unused doc comment
1 => {},
#[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment
_ => {}
}

@@ -32,6 +38,7 @@ fn foo() {
/// bar //~ ERROR unused doc comment
mac!();

#[doc(test(attr(allow(dead_code))))] //~ ERROR unused doc comment
let x = /** comment */ 47; //~ ERROR unused doc comment

/// dox //~ ERROR unused doc comment
73 changes: 63 additions & 10 deletions tests/ui/lint/unused/useless-comment.stderr
Original file line number Diff line number Diff line change
@@ -12,25 +12,57 @@ LL | #![deny(unused_doc_comments)]
| ^^^^^^^^^^^^^^^^^^^

error: unused doc comment
--> $DIR/useless-comment.rs:32:5
--> $DIR/useless-comment.rs:12:1
|
LL | /// a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | #[doc(test(attr(allow(dead_code))))]
LL | unsafe extern "C" { }
| --------------------- rustdoc does not generate documentation for extern blocks
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:13:1
|
LL | #[doc(test(attr(allow(dead_code))))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | unsafe extern "C" { }
| --------------------- rustdoc does not generate documentation for extern blocks
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:38:5
|
LL | /// bar
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ rustdoc does not generate documentation for macro invocations
|
= help: to document an item produced by a macro, the macro must produce the documentation as part of its expansion

error: unused doc comment
--> $DIR/useless-comment.rs:13:5
--> $DIR/useless-comment.rs:17:5
|
LL | /// a
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | #[doc(test(attr(allow(dead_code))))]
LL | let x = 12;
| ----------- rustdoc does not generate documentation for statements
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:16:5
--> $DIR/useless-comment.rs:18:5
|
LL | #[doc(test(attr(allow(dead_code))))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | let x = 12;
| ----------- rustdoc does not generate documentation for statements
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:21:5
|
LL | / /// multi-line
LL | | /// doc comment
@@ -39,14 +71,15 @@ LL | | /// that is unused
LL | / match x {
LL | | /// c
LL | | 1 => {},
LL | | #[doc(test(attr(allow(dead_code))))]
LL | | _ => {}
LL | | }
| |_____- rustdoc does not generate documentation for expressions
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:20:9
--> $DIR/useless-comment.rs:25:9
|
LL | /// c
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -56,7 +89,17 @@ LL | 1 => {},
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:25:5
--> $DIR/useless-comment.rs:27:9
|
LL | #[doc(test(attr(allow(dead_code))))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | _ => {}
| ------- rustdoc does not generate documentation for match arms
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:31:5
|
LL | /// foo
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -66,7 +109,7 @@ LL | unsafe {}
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:28:5
--> $DIR/useless-comment.rs:34:5
|
LL | #[doc = "foo"]
| ^^^^^^^^^^^^^^
@@ -77,7 +120,7 @@ LL | 3;
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:29:5
--> $DIR/useless-comment.rs:35:5
|
LL | #[doc = "bar"]
| ^^^^^^^^^^^^^^
@@ -87,15 +130,25 @@ LL | 3;
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:35:13
--> $DIR/useless-comment.rs:41:5
|
LL | #[doc(test(attr(allow(dead_code))))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | let x = /** comment */ 47;
| -------------------------- rustdoc does not generate documentation for statements
|
= help: use `//` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:42:13
|
LL | let x = /** comment */ 47;
| ^^^^^^^^^^^^^^ -- rustdoc does not generate documentation for expressions
|
= help: use `/* */` for a plain comment

error: unused doc comment
--> $DIR/useless-comment.rs:37:5
--> $DIR/useless-comment.rs:44:5
|
LL | /// dox
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -106,5 +159,5 @@ LL | | }
|
= help: use `//` for a plain comment

error: aborting due to 10 previous errors
error: aborting due to 15 previous errors

42 changes: 42 additions & 0 deletions tests/ui/rustdoc/doc-test-attr-pass.rs
Original file line number Diff line number Diff line change
@@ -6,4 +6,46 @@
#![doc(test(attr(deny(warnings))))]
#![doc(test())]

mod test {
#![doc(test(attr(allow(warnings))))]
}

#[doc(test(attr(allow(dead_code))))]
static S: u32 = 5;

#[doc(test(attr(allow(dead_code))))]
const C: u32 = 5;

#[doc(test(attr(deny(dead_code))))]
struct A {
#[doc(test(attr(allow(dead_code))))]
field: u32
}

#[doc(test(attr(deny(dead_code))))]
union U {
#[doc(test(attr(allow(dead_code))))]
field: u32,
field2: u64,
}

#[doc(test(attr(deny(dead_code))))]
enum Enum {
#[doc(test(attr(allow(dead_code))))]
Variant1,
}

#[doc(test(attr(deny(dead_code))))]
impl A {
#[doc(test(attr(deny(dead_code))))]
fn method() {}
}

#[doc(test(attr(deny(dead_code))))]
trait MyTrait {
#[doc(test(attr(deny(dead_code))))]
fn my_trait_fn();
}

#[doc(test(attr(deny(dead_code))))]
pub fn foo() {}