Skip to content

Commit 5cf4129

Browse files
Emit a warning if the doctest main function will not be run
1 parent d2eadb7 commit 5cf4129

File tree

8 files changed

+85
-17
lines changed

8 files changed

+85
-17
lines changed

src/librustdoc/doctest.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ use rustc_hir::def_id::LOCAL_CRATE;
2323
use rustc_interface::interface;
2424
use rustc_session::config::{self, CrateType, ErrorOutputType, Input};
2525
use rustc_session::lint;
26-
use rustc_span::FileName;
2726
use rustc_span::edition::Edition;
2827
use rustc_span::symbol::sym;
28+
use rustc_span::{FileName, Span};
2929
use rustc_target::spec::{Target, TargetTuple};
3030
use tempfile::{Builder as TempFileBuilder, TempDir};
3131
use tracing::debug;
@@ -239,7 +239,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
239239
}
240240
} else {
241241
let mut collector = CreateRunnableDocTests::new(options, opts);
242-
tests.into_iter().for_each(|t| collector.add_test(t));
242+
tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx())));
243243

244244
Ok(Some(collector))
245245
}
@@ -872,6 +872,7 @@ pub(crate) struct ScrapedDocTest {
872872
langstr: LangString,
873873
text: String,
874874
name: String,
875+
span: Span,
875876
}
876877

877878
impl ScrapedDocTest {
@@ -881,6 +882,7 @@ impl ScrapedDocTest {
881882
logical_path: Vec<String>,
882883
langstr: LangString,
883884
text: String,
885+
span: Span,
884886
) -> Self {
885887
let mut item_path = logical_path.join("::");
886888
item_path.retain(|c| c != ' ');
@@ -890,7 +892,7 @@ impl ScrapedDocTest {
890892
let name =
891893
format!("{} - {item_path}(line {line})", filename.prefer_remapped_unconditionaly());
892894

893-
Self { filename, line, langstr, text, name }
895+
Self { filename, line, langstr, text, name, span }
894896
}
895897
fn edition(&self, opts: &RustdocOptions) -> Edition {
896898
self.langstr.edition.unwrap_or(opts.edition)
@@ -946,7 +948,7 @@ impl CreateRunnableDocTests {
946948
}
947949
}
948950

949-
fn add_test(&mut self, scraped_test: ScrapedDocTest) {
951+
fn add_test(&mut self, scraped_test: ScrapedDocTest, dcx: Option<DiagCtxtHandle<'_>>) {
950952
// For example `module/file.rs` would become `module_file_rs`
951953
let file = scraped_test
952954
.filename
@@ -977,6 +979,8 @@ impl CreateRunnableDocTests {
977979
self.can_merge_doctests,
978980
Some(test_id),
979981
Some(&scraped_test.langstr),
982+
dcx,
983+
scraped_test.span,
980984
);
981985
let is_standalone = !doctest.can_be_merged
982986
|| scraped_test.langstr.compile_fail

src/librustdoc/doctest/extracted.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This module contains the logic to extract doctests and output a JSON containing this
44
//! information.
55
6+
use rustc_span::DUMMY_SP;
67
use serde::Serialize;
78

89
use super::{DocTestBuilder, ScrapedDocTest};
@@ -35,7 +36,7 @@ impl ExtractedDocTests {
3536
) {
3637
let edition = scraped_test.edition(options);
3738

38-
let ScrapedDocTest { filename, line, langstr, text, name } = scraped_test;
39+
let ScrapedDocTest { filename, line, langstr, text, name, .. } = scraped_test;
3940

4041
let doctest = DocTestBuilder::new(
4142
&text,
@@ -44,6 +45,8 @@ impl ExtractedDocTests {
4445
false,
4546
None,
4647
Some(&langstr),
48+
None,
49+
DUMMY_SP,
4750
);
4851
let (full_test_code, size) = doctest.generate_unique_doctest(
4952
&text,

src/librustdoc/doctest/make.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ use std::sync::Arc;
88
use rustc_ast::token::{Delimiter, TokenKind};
99
use rustc_ast::tokenstream::TokenTree;
1010
use rustc_ast::{self as ast, AttrStyle, HasAttrs, StmtKind};
11-
use rustc_errors::ColorConfig;
1211
use rustc_errors::emitter::stderr_destination;
12+
use rustc_errors::{ColorConfig, DiagCtxtHandle};
1313
use rustc_parse::new_parser_from_source_str;
1414
use rustc_session::parse::ParseSess;
1515
use rustc_span::edition::Edition;
1616
use rustc_span::source_map::SourceMap;
1717
use rustc_span::symbol::sym;
18-
use rustc_span::{FileName, kw};
18+
use rustc_span::{FileName, Span, kw};
1919
use tracing::debug;
2020

2121
use super::GlobalTestOptions;
@@ -61,6 +61,8 @@ impl DocTestBuilder {
6161
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
6262
test_id: Option<String>,
6363
lang_str: Option<&LangString>,
64+
dcx: Option<DiagCtxtHandle<'_>>,
65+
span: Span,
6466
) -> Self {
6567
let can_merge_doctests = can_merge_doctests
6668
&& lang_str.is_some_and(|lang_str| {
@@ -69,7 +71,7 @@ impl DocTestBuilder {
6971

7072
let result = rustc_driver::catch_fatal_errors(|| {
7173
rustc_span::create_session_if_not_set_then(edition, |_| {
72-
parse_source(source, &crate_name)
74+
parse_source(source, &crate_name, dcx, span)
7375
})
7476
});
7577

@@ -289,7 +291,12 @@ fn reset_error_count(psess: &ParseSess) {
289291

290292
const DOCTEST_CODE_WRAPPER: &str = "fn f(){";
291293

292-
fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceInfo, ()> {
294+
fn parse_source(
295+
source: &str,
296+
crate_name: &Option<&str>,
297+
parent_dcx: Option<DiagCtxtHandle<'_>>,
298+
span: Span,
299+
) -> Result<ParseSourceInfo, ()> {
293300
use rustc_errors::DiagCtxt;
294301
use rustc_errors::emitter::{Emitter, HumanEmitter};
295302
use rustc_span::source_map::FilePathMapping;
@@ -475,8 +482,17 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
475482
}
476483
}
477484
if has_non_items {
478-
// FIXME: if `info.has_main_fn` is `true`, emit a warning here to mention that
479-
// this code will not be called.
485+
if info.has_main_fn
486+
&& let Some(dcx) = parent_dcx
487+
&& !span.is_dummy()
488+
{
489+
dcx.span_warn(
490+
span,
491+
"the `main` function of this doctest won't be run as it contains \
492+
expressions at the top level, meaning that the whole doctest code will be \
493+
wrapped in a function",
494+
);
495+
}
480496
info.has_main_fn = false;
481497
}
482498
Ok(info)

src/librustdoc/doctest/markdown.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::fs::read_to_string;
44
use std::sync::{Arc, Mutex};
55

66
use rustc_session::config::Input;
7-
use rustc_span::FileName;
7+
use rustc_span::{DUMMY_SP, FileName};
88
use tempfile::tempdir;
99

1010
use super::{
@@ -24,7 +24,14 @@ impl DocTestVisitor for MdCollector {
2424
let filename = self.filename.clone();
2525
// First line of Markdown is line 1.
2626
let line = 1 + rel_line.offset();
27-
self.tests.push(ScrapedDocTest::new(filename, line, self.cur_path.clone(), config, test));
27+
self.tests.push(ScrapedDocTest::new(
28+
filename,
29+
line,
30+
self.cur_path.clone(),
31+
config,
32+
test,
33+
DUMMY_SP,
34+
));
2835
}
2936

3037
fn visit_header(&mut self, name: &str, level: u32) {
@@ -107,7 +114,7 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
107114
find_testable_code(&input_str, &mut md_collector, codes, None);
108115

109116
let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
110-
md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
117+
md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
111118
let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
112119
collector;
113120
crate::doctest::run_tests(

src/librustdoc/doctest/rust.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use std::env;
44
use std::sync::Arc;
5+
use std::sync::atomic::{AtomicUsize, Ordering};
56

67
use rustc_data_structures::fx::FxHashSet;
78
use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId};
@@ -47,13 +48,32 @@ impl RustCollector {
4748

4849
impl DocTestVisitor for RustCollector {
4950
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
50-
let line = self.get_base_line() + rel_line.offset();
51+
let base_line = self.get_base_line();
52+
let line = base_line + rel_line.offset();
53+
let count = AtomicUsize::new(base_line);
54+
let span = if line > base_line {
55+
match self.source_map.span_extend_while(self.position, |c| {
56+
if c == '\n' {
57+
let count = count.fetch_add(1, Ordering::SeqCst);
58+
if count >= line {
59+
return false;
60+
}
61+
}
62+
true
63+
}) {
64+
Ok(sp) => self.source_map.span_extend_to_line(sp.shrink_to_hi()),
65+
_ => self.position,
66+
}
67+
} else {
68+
self.position
69+
};
5170
self.tests.push(ScrapedDocTest::new(
5271
self.get_filename(),
5372
line,
5473
self.cur_path.clone(),
5574
config,
5675
test,
76+
span,
5777
));
5878
}
5979

src/librustdoc/html/markdown.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ use rustc_middle::ty::TyCtxt;
4545
pub(crate) use rustc_resolve::rustdoc::main_body_opts;
4646
use rustc_resolve::rustdoc::may_be_doc_link;
4747
use rustc_span::edition::Edition;
48-
use rustc_span::{Span, Symbol};
48+
use rustc_span::{DUMMY_SP, Span, Symbol};
4949
use tracing::{debug, trace};
5050

5151
use crate::clean::RenderedLink;
@@ -303,7 +303,9 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
303303
attrs: vec![],
304304
args_file: PathBuf::new(),
305305
};
306-
let doctest = doctest::DocTestBuilder::new(&test, krate, edition, false, None, None);
306+
let doctest = doctest::DocTestBuilder::new(
307+
&test, krate, edition, false, None, None, None, DUMMY_SP,
308+
);
307309
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
308310
let channel = if test.contains("#![feature(") { "&amp;version=nightly" } else { "" };
309311

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
warning: the `main` function of this doctest won't be run as it contains expressions at the top level, meaning that the whole doctest code will be wrapped in a function
2+
--> $DIR/failed-doctest-extra-semicolon-on-item.rs:11:1
3+
|
4+
11 | /// ```rust
5+
| ^^^^^^^^^^^
6+
7+
warning: 1 warning emitted
8+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
warning: the `main` function of this doctest won't be run as it contains expressions at the top level, meaning that the whole doctest code will be wrapped in a function
2+
--> $DIR/test-main-alongside-exprs.rs:15:1
3+
|
4+
15 | //! ```
5+
| ^^^^^^^
6+
7+
warning: 1 warning emitted
8+

0 commit comments

Comments
 (0)