diff options
| author | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-04-30 20:29:29 +0200 |
|---|---|---|
| committer | Guillaume Gomez <guillaume1.gomez@gmail.com> | 2025-05-02 13:57:53 +0200 |
| commit | b791eaa4480a8e3acffe3faad4de0462b8476aca (patch) | |
| tree | 9006cd73b19e274dba4869d8e047e36ab80bd503 | |
| parent | cb0d6e76d0515b19d249c0147d246296b9d3d124 (diff) | |
| download | rust-b791eaa4480a8e3acffe3faad4de0462b8476aca.tar.gz rust-b791eaa4480a8e3acffe3faad4de0462b8476aca.zip | |
Emit a warning if the doctest `main` function will not be run
| -rw-r--r-- | src/librustdoc/doctest.rs | 12 | ||||
| -rw-r--r-- | src/librustdoc/doctest/extracted.rs | 5 | ||||
| -rw-r--r-- | src/librustdoc/doctest/make.rs | 28 | ||||
| -rw-r--r-- | src/librustdoc/doctest/markdown.rs | 13 | ||||
| -rw-r--r-- | src/librustdoc/doctest/rust.rs | 23 | ||||
| -rw-r--r-- | src/librustdoc/doctest/tests.rs | 3 | ||||
| -rw-r--r-- | src/librustdoc/html/markdown.rs | 6 | ||||
| -rw-r--r-- | tests/rustdoc-ui/doctest/failed-doctest-extra-semicolon-on-item.stderr | 8 | ||||
| -rw-r--r-- | tests/rustdoc-ui/doctest/main-alongside-stmts.stderr | 14 | ||||
| -rw-r--r-- | tests/rustdoc-ui/doctest/test-main-alongside-exprs.stderr | 8 |
10 files changed, 103 insertions, 17 deletions
diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 829a9ca6e7d..5b85eb54a5c 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -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 diff --git a/src/librustdoc/doctest/extracted.rs b/src/librustdoc/doctest/extracted.rs index ce362eabfc4..d82bca3279d 100644 --- a/src/librustdoc/doctest/extracted.rs +++ b/src/librustdoc/doctest/extracted.rs @@ -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, diff --git a/src/librustdoc/doctest/make.rs b/src/librustdoc/doctest/make.rs index d4fbfb12582..759b139e288 100644 --- a/src/librustdoc/doctest/make.rs +++ b/src/librustdoc/doctest/make.rs @@ -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) diff --git a/src/librustdoc/doctest/markdown.rs b/src/librustdoc/doctest/markdown.rs index 497a8d7c4a7..b877e34583f 100644 --- a/src/librustdoc/doctest/markdown.rs +++ b/src/librustdoc/doctest/markdown.rs @@ -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( diff --git a/src/librustdoc/doctest/rust.rs b/src/librustdoc/doctest/rust.rs index 43dcfab880b..f9d2aa3d3b4 100644 --- a/src/librustdoc/doctest/rust.rs +++ b/src/librustdoc/doctest/rust.rs @@ -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, )); } diff --git a/src/librustdoc/doctest/tests.rs b/src/librustdoc/doctest/tests.rs index 49add73e9d6..ce27a20540e 100644 --- a/src/librustdoc/doctest/tests.rs +++ b/src/librustdoc/doctest/tests.rs @@ -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); diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index fc46293e7ea..5014a5198c8 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -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("#