Emit a warning if the doctest main function will not be run
This commit is contained in:
parent
cb0d6e76d0
commit
b791eaa448
10 changed files with 103 additions and 17 deletions
|
|
@ -23,9 +23,9 @@ use rustc_hir::def_id::LOCAL_CRATE;
|
|||
use rustc_interface::interface;
|
||||
use rustc_session::config::{self, CrateType, ErrorOutputType, Input};
|
||||
use rustc_session::lint;
|
||||
use rustc_span::FileName;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{FileName, Span};
|
||||
use rustc_target::spec::{Target, TargetTuple};
|
||||
use tempfile::{Builder as TempFileBuilder, TempDir};
|
||||
use tracing::debug;
|
||||
|
|
@ -239,7 +239,7 @@ pub(crate) fn run(dcx: DiagCtxtHandle<'_>, input: Input, options: RustdocOptions
|
|||
}
|
||||
} else {
|
||||
let mut collector = CreateRunnableDocTests::new(options, opts);
|
||||
tests.into_iter().for_each(|t| collector.add_test(t));
|
||||
tests.into_iter().for_each(|t| collector.add_test(t, Some(compiler.sess.dcx())));
|
||||
|
||||
Ok(Some(collector))
|
||||
}
|
||||
|
|
@ -872,6 +872,7 @@ pub(crate) struct ScrapedDocTest {
|
|||
langstr: LangString,
|
||||
text: String,
|
||||
name: String,
|
||||
span: Span,
|
||||
}
|
||||
|
||||
impl ScrapedDocTest {
|
||||
|
|
@ -881,6 +882,7 @@ impl ScrapedDocTest {
|
|||
logical_path: Vec<String>,
|
||||
langstr: LangString,
|
||||
text: String,
|
||||
span: Span,
|
||||
) -> Self {
|
||||
let mut item_path = logical_path.join("::");
|
||||
item_path.retain(|c| c != ' ');
|
||||
|
|
@ -890,7 +892,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, span }
|
||||
}
|
||||
fn edition(&self, opts: &RustdocOptions) -> Edition {
|
||||
self.langstr.edition.unwrap_or(opts.edition)
|
||||
|
|
@ -946,7 +948,7 @@ impl CreateRunnableDocTests {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_test(&mut self, scraped_test: ScrapedDocTest) {
|
||||
fn add_test(&mut self, scraped_test: ScrapedDocTest, dcx: Option<DiagCtxtHandle<'_>>) {
|
||||
// For example `module/file.rs` would become `module_file_rs`
|
||||
let file = scraped_test
|
||||
.filename
|
||||
|
|
@ -977,6 +979,8 @@ impl CreateRunnableDocTests {
|
|||
self.can_merge_doctests,
|
||||
Some(test_id),
|
||||
Some(&scraped_test.langstr),
|
||||
dcx,
|
||||
scraped_test.span,
|
||||
);
|
||||
let is_standalone = !doctest.can_be_merged
|
||||
|| scraped_test.langstr.compile_fail
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
//! This module contains the logic to extract doctests and output a JSON containing this
|
||||
//! information.
|
||||
|
||||
use rustc_span::DUMMY_SP;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{DocTestBuilder, ScrapedDocTest};
|
||||
|
|
@ -35,7 +36,7 @@ impl ExtractedDocTests {
|
|||
) {
|
||||
let edition = scraped_test.edition(options);
|
||||
|
||||
let ScrapedDocTest { filename, line, langstr, text, name } = scraped_test;
|
||||
let ScrapedDocTest { filename, line, langstr, text, name, .. } = scraped_test;
|
||||
|
||||
let doctest = DocTestBuilder::new(
|
||||
&text,
|
||||
|
|
@ -44,6 +45,8 @@ impl ExtractedDocTests {
|
|||
false,
|
||||
None,
|
||||
Some(&langstr),
|
||||
None,
|
||||
DUMMY_SP,
|
||||
);
|
||||
let (full_test_code, size) = doctest.generate_unique_doctest(
|
||||
&text,
|
||||
|
|
|
|||
|
|
@ -8,14 +8,14 @@ use std::sync::Arc;
|
|||
use rustc_ast::token::{Delimiter, TokenKind};
|
||||
use rustc_ast::tokenstream::TokenTree;
|
||||
use rustc_ast::{self as ast, AttrStyle, HasAttrs, StmtKind};
|
||||
use rustc_errors::ColorConfig;
|
||||
use rustc_errors::emitter::stderr_destination;
|
||||
use rustc_errors::{ColorConfig, DiagCtxtHandle};
|
||||
use rustc_parse::new_parser_from_source_str;
|
||||
use rustc_session::parse::ParseSess;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::source_map::SourceMap;
|
||||
use rustc_span::symbol::sym;
|
||||
use rustc_span::{FileName, kw};
|
||||
use rustc_span::{FileName, Span, kw};
|
||||
use tracing::debug;
|
||||
|
||||
use super::GlobalTestOptions;
|
||||
|
|
@ -61,6 +61,8 @@ impl DocTestBuilder {
|
|||
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
|
||||
test_id: Option<String>,
|
||||
lang_str: Option<&LangString>,
|
||||
dcx: Option<DiagCtxtHandle<'_>>,
|
||||
span: Span,
|
||||
) -> Self {
|
||||
let can_merge_doctests = can_merge_doctests
|
||||
&& lang_str.is_some_and(|lang_str| {
|
||||
|
|
@ -69,7 +71,7 @@ impl DocTestBuilder {
|
|||
|
||||
let result = rustc_driver::catch_fatal_errors(|| {
|
||||
rustc_span::create_session_if_not_set_then(edition, |_| {
|
||||
parse_source(source, &crate_name)
|
||||
parse_source(source, &crate_name, dcx, span)
|
||||
})
|
||||
});
|
||||
|
||||
|
|
@ -289,7 +291,12 @@ fn reset_error_count(psess: &ParseSess) {
|
|||
|
||||
const DOCTEST_CODE_WRAPPER: &str = "fn f(){";
|
||||
|
||||
fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceInfo, ()> {
|
||||
fn parse_source(
|
||||
source: &str,
|
||||
crate_name: &Option<&str>,
|
||||
parent_dcx: Option<DiagCtxtHandle<'_>>,
|
||||
span: Span,
|
||||
) -> Result<ParseSourceInfo, ()> {
|
||||
use rustc_errors::DiagCtxt;
|
||||
use rustc_errors::emitter::{Emitter, HumanEmitter};
|
||||
use rustc_span::source_map::FilePathMapping;
|
||||
|
|
@ -466,8 +473,17 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
|
|||
}
|
||||
}
|
||||
if has_non_items {
|
||||
// FIXME: if `info.has_main_fn` is `true`, emit a warning here to mention that
|
||||
// this code will not be called.
|
||||
if info.has_main_fn
|
||||
&& let Some(dcx) = parent_dcx
|
||||
&& !span.is_dummy()
|
||||
{
|
||||
dcx.span_warn(
|
||||
span,
|
||||
"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",
|
||||
);
|
||||
}
|
||||
info.has_main_fn = false;
|
||||
}
|
||||
Ok(info)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use std::fs::read_to_string;
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use rustc_session::config::Input;
|
||||
use rustc_span::FileName;
|
||||
use rustc_span::{DUMMY_SP, FileName};
|
||||
use tempfile::tempdir;
|
||||
|
||||
use super::{
|
||||
|
|
@ -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,
|
||||
DUMMY_SP,
|
||||
));
|
||||
}
|
||||
|
||||
fn visit_header(&mut self, name: &str, level: u32) {
|
||||
|
|
@ -107,7 +114,7 @@ pub(crate) fn test(input: &Input, options: Options) -> Result<(), String> {
|
|||
find_testable_code(&input_str, &mut md_collector, codes, None);
|
||||
|
||||
let mut collector = CreateRunnableDocTests::new(options.clone(), opts);
|
||||
md_collector.tests.into_iter().for_each(|t| collector.add_test(t));
|
||||
md_collector.tests.into_iter().for_each(|t| collector.add_test(t, None));
|
||||
let CreateRunnableDocTests { opts, rustdoc_options, standalone_tests, mergeable_tests, .. } =
|
||||
collector;
|
||||
crate::doctest::run_tests(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//! Doctest functionality used only for doctests in `.rs` source files.
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
|
|
@ -47,13 +48,33 @@ impl RustCollector {
|
|||
|
||||
impl DocTestVisitor for RustCollector {
|
||||
fn visit_test(&mut self, test: String, config: LangString, rel_line: MdRelLine) {
|
||||
let line = self.get_base_line() + rel_line.offset();
|
||||
let base_line = self.get_base_line();
|
||||
let line = base_line + rel_line.offset();
|
||||
let count = Cell::new(base_line);
|
||||
let span = if line > base_line {
|
||||
match self.source_map.span_extend_while(self.position, |c| {
|
||||
if c == '\n' {
|
||||
let count_v = count.get();
|
||||
count.set(count_v + 1);
|
||||
if count_v >= line {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}) {
|
||||
Ok(sp) => self.source_map.span_extend_to_line(sp.shrink_to_hi()),
|
||||
_ => self.position,
|
||||
}
|
||||
} else {
|
||||
self.position
|
||||
};
|
||||
self.tests.push(ScrapedDocTest::new(
|
||||
self.get_filename(),
|
||||
line,
|
||||
self.cur_path.clone(),
|
||||
config,
|
||||
test,
|
||||
span,
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use rustc_span::DUMMY_SP;
|
||||
use rustc_span::edition::DEFAULT_EDITION;
|
||||
|
||||
use super::{DocTestBuilder, GlobalTestOptions};
|
||||
|
|
@ -18,6 +19,8 @@ fn make_test(
|
|||
false,
|
||||
test_id.map(|s| s.to_string()),
|
||||
None,
|
||||
None,
|
||||
DUMMY_SP,
|
||||
);
|
||||
let (code, line_offset) =
|
||||
doctest.generate_unique_doctest(test_code, dont_insert_main, opts, crate_name);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ use rustc_middle::ty::TyCtxt;
|
|||
pub(crate) use rustc_resolve::rustdoc::main_body_opts;
|
||||
use rustc_resolve::rustdoc::may_be_doc_link;
|
||||
use rustc_span::edition::Edition;
|
||||
use rustc_span::{Span, Symbol};
|
||||
use rustc_span::{DUMMY_SP, Span, Symbol};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::clean::RenderedLink;
|
||||
|
|
@ -303,7 +303,9 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> {
|
|||
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, false, None, None, None, DUMMY_SP,
|
||||
);
|
||||
let (test, _) = doctest.generate_unique_doctest(&test, false, &opts, krate);
|
||||
let channel = if test.contains("#![feature(") { "&version=nightly" } else { "" };
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
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
|
||||
--> $DIR/failed-doctest-extra-semicolon-on-item.rs:11:1
|
||||
|
|
||||
11 | /// ```rust
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
||||
14
tests/rustdoc-ui/doctest/main-alongside-stmts.stderr
Normal file
14
tests/rustdoc-ui/doctest/main-alongside-stmts.stderr
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
--> $DIR/main-alongside-stmts.rs:17:1
|
||||
|
|
||||
17 | //! ```
|
||||
| ^^^^^^^
|
||||
|
||||
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
|
||||
--> $DIR/main-alongside-stmts.rs:26:1
|
||||
|
|
||||
26 | //! ```
|
||||
| ^^^^^^^
|
||||
|
||||
warning: 2 warnings emitted
|
||||
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
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
|
||||
--> $DIR/test-main-alongside-exprs.rs:15:1
|
||||
|
|
||||
15 | //! ```
|
||||
| ^^^^^^^
|
||||
|
||||
warning: 1 warning emitted
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue