"Fossies" - the Fresh Open Source Software Archive

Member "rustc-1.61.0-src/src/librustdoc/doctest.rs" (18 May 2022, 48156 Bytes) of package /linux/misc/rustc-1.61.0-src.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Rust source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "doctest.rs": 1.60.0_vs_1.61.0.

    1 use rustc_ast as ast;
    2 use rustc_data_structures::fx::{FxHashMap, FxHashSet};
    3 use rustc_data_structures::sync::Lrc;
    4 use rustc_errors::{ColorConfig, ErrorGuaranteed, FatalError};
    5 use rustc_hir as hir;
    6 use rustc_hir::def_id::LOCAL_CRATE;
    7 use rustc_hir::intravisit;
    8 use rustc_hir::{HirId, CRATE_HIR_ID};
    9 use rustc_interface::interface;
   10 use rustc_middle::hir::map::Map;
   11 use rustc_middle::hir::nested_filter;
   12 use rustc_middle::ty::TyCtxt;
   13 use rustc_parse::maybe_new_parser_from_source_str;
   14 use rustc_parse::parser::attr::InnerAttrPolicy;
   15 use rustc_session::config::{self, CrateType, ErrorOutputType};
   16 use rustc_session::parse::ParseSess;
   17 use rustc_session::{lint, DiagnosticOutput, Session};
   18 use rustc_span::edition::Edition;
   19 use rustc_span::source_map::SourceMap;
   20 use rustc_span::symbol::sym;
   21 use rustc_span::Symbol;
   22 use rustc_span::{BytePos, FileName, Pos, Span, DUMMY_SP};
   23 use rustc_target::spec::TargetTriple;
   24 use tempfile::Builder as TempFileBuilder;
   25 
   26 use std::env;
   27 use std::io::{self, Write};
   28 use std::panic;
   29 use std::path::PathBuf;
   30 use std::process::{self, Command, Stdio};
   31 use std::str;
   32 use std::sync::atomic::{AtomicUsize, Ordering};
   33 use std::sync::{Arc, Mutex};
   34 
   35 use crate::clean::{types::AttributesExt, Attributes};
   36 use crate::config::Options as RustdocOptions;
   37 use crate::html::markdown::{self, ErrorCodes, Ignore, LangString};
   38 use crate::lint::init_lints;
   39 use crate::passes::span_of_attrs;
   40 
   41 /// Options that apply to all doctests in a crate or Markdown file (for `rustdoc foo.md`).
   42 #[derive(Clone, Default)]
   43 crate struct GlobalTestOptions {
   44     /// Whether to disable the default `extern crate my_crate;` when creating doctests.
   45     crate no_crate_inject: bool,
   46     /// Additional crate-level attributes to add to doctests.
   47     crate attrs: Vec<String>,
   48 }
   49 
   50 crate fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> {
   51     let input = config::Input::File(options.input.clone());
   52 
   53     let invalid_codeblock_attributes_name = crate::lint::INVALID_CODEBLOCK_ATTRIBUTES.name;
   54 
   55     // See core::create_config for what's going on here.
   56     let allowed_lints = vec![
   57         invalid_codeblock_attributes_name.to_owned(),
   58         lint::builtin::UNKNOWN_LINTS.name.to_owned(),
   59         lint::builtin::RENAMED_AND_REMOVED_LINTS.name.to_owned(),
   60     ];
   61 
   62     let (lint_opts, lint_caps) = init_lints(allowed_lints, options.lint_opts.clone(), |lint| {
   63         if lint.name == invalid_codeblock_attributes_name {
   64             None
   65         } else {
   66             Some((lint.name_lower(), lint::Allow))
   67         }
   68     });
   69 
   70     debug!(?lint_opts);
   71 
   72     let crate_types =
   73         if options.proc_macro_crate { vec![CrateType::ProcMacro] } else { vec![CrateType::Rlib] };
   74 
   75     let sessopts = config::Options {
   76         maybe_sysroot: options.maybe_sysroot.clone(),
   77         search_paths: options.libs.clone(),
   78         crate_types,
   79         lint_opts,
   80         lint_cap: Some(options.lint_cap.unwrap_or(lint::Forbid)),
   81         cg: options.codegen_options.clone(),
   82         externs: options.externs.clone(),
   83         unstable_features: options.render_options.unstable_features,
   84         actually_rustdoc: true,
   85         edition: options.edition,
   86         target_triple: options.target.clone(),
   87         crate_name: options.crate_name.clone(),
   88         ..config::Options::default()
   89     };
   90 
   91     let mut cfgs = options.cfgs.clone();
   92     cfgs.push("doc".to_owned());
   93     cfgs.push("doctest".to_owned());
   94     let config = interface::Config {
   95         opts: sessopts,
   96         crate_cfg: interface::parse_cfgspecs(cfgs),
   97         crate_check_cfg: interface::parse_check_cfg(options.check_cfgs.clone()),
   98         input,
   99         input_path: None,
  100         output_file: None,
  101         output_dir: None,
  102         file_loader: None,
  103         diagnostic_output: DiagnosticOutput::Default,
  104         lint_caps,
  105         parse_sess_created: None,
  106         register_lints: Some(box crate::lint::register_lints),
  107         override_queries: None,
  108         make_codegen_backend: None,
  109         registry: rustc_driver::diagnostics_registry(),
  110     };
  111 
  112     let test_args = options.test_args.clone();
  113     let nocapture = options.nocapture;
  114     let externs = options.externs.clone();
  115     let json_unused_externs = options.json_unused_externs;
  116 
  117     let (tests, unused_extern_reports, compiling_test_count) =
  118         interface::run_compiler(config, |compiler| {
  119             compiler.enter(|queries| {
  120                 let mut global_ctxt = queries.global_ctxt()?.take();
  121 
  122                 let collector = global_ctxt.enter(|tcx| {
  123                     let crate_attrs = tcx.hir().attrs(CRATE_HIR_ID);
  124 
  125                     let opts = scrape_test_config(crate_attrs);
  126                     let enable_per_target_ignores = options.enable_per_target_ignores;
  127                     let mut collector = Collector::new(
  128                         tcx.crate_name(LOCAL_CRATE),
  129                         options,
  130                         false,
  131                         opts,
  132                         Some(compiler.session().parse_sess.clone_source_map()),
  133                         None,
  134                         enable_per_target_ignores,
  135                     );
  136 
  137                     let mut hir_collector = HirCollector {
  138                         sess: compiler.session(),
  139                         collector: &mut collector,
  140                         map: tcx.hir(),
  141                         codes: ErrorCodes::from(
  142                             compiler.session().opts.unstable_features.is_nightly_build(),
  143                         ),
  144                         tcx,
  145                     };
  146                     hir_collector.visit_testable(
  147                         "".to_string(),
  148                         CRATE_HIR_ID,
  149                         tcx.hir().span(CRATE_HIR_ID),
  150                         |this| tcx.hir().walk_toplevel_module(this),
  151                     );
  152 
  153                     collector
  154                 });
  155                 if compiler.session().diagnostic().has_errors_or_lint_errors().is_some() {
  156                     FatalError.raise();
  157                 }
  158 
  159                 let unused_extern_reports = collector.unused_extern_reports.clone();
  160                 let compiling_test_count = collector.compiling_test_count.load(Ordering::SeqCst);
  161                 let ret: Result<_, ErrorGuaranteed> =
  162                     Ok((collector.tests, unused_extern_reports, compiling_test_count));
  163                 ret
  164             })
  165         })?;
  166 
  167     run_tests(test_args, nocapture, tests);
  168 
  169     // Collect and warn about unused externs, but only if we've gotten
  170     // reports for each doctest
  171     if json_unused_externs {
  172         let unused_extern_reports: Vec<_> =
  173             std::mem::take(&mut unused_extern_reports.lock().unwrap());
  174         if unused_extern_reports.len() == compiling_test_count {
  175             let extern_names = externs.iter().map(|(name, _)| name).collect::<FxHashSet<&String>>();
  176             let mut unused_extern_names = unused_extern_reports
  177                 .iter()
  178                 .map(|uexts| uexts.unused_extern_names.iter().collect::<FxHashSet<&String>>())
  179                 .fold(extern_names, |uextsa, uextsb| {
  180                     uextsa.intersection(&uextsb).copied().collect::<FxHashSet<&String>>()
  181                 })
  182                 .iter()
  183                 .map(|v| (*v).clone())
  184                 .collect::<Vec<String>>();
  185             unused_extern_names.sort();
  186             // Take the most severe lint level
  187             let lint_level = unused_extern_reports
  188                 .iter()
  189                 .map(|uexts| uexts.lint_level.as_str())
  190                 .max_by_key(|v| match *v {
  191                     "warn" => 1,
  192                     "deny" => 2,
  193                     "forbid" => 3,
  194                     // The allow lint level is not expected,
  195                     // as if allow is specified, no message
  196                     // is to be emitted.
  197                     v => unreachable!("Invalid lint level '{}'", v),
  198                 })
  199                 .unwrap_or("warn")
  200                 .to_string();
  201             let uext = UnusedExterns { lint_level, unused_extern_names };
  202             let unused_extern_json = serde_json::to_string(&uext).unwrap();
  203             eprintln!("{unused_extern_json}");
  204         }
  205     }
  206 
  207     Ok(())
  208 }
  209 
  210 crate fn run_tests(mut test_args: Vec<String>, nocapture: bool, tests: Vec<test::TestDescAndFn>) {
  211     test_args.insert(0, "rustdoctest".to_string());
  212     if nocapture {
  213         test_args.push("--nocapture".to_string());
  214     }
  215     test::test_main(&test_args, tests, None);
  216 }
  217 
  218 // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
  219 fn scrape_test_config(attrs: &[ast::Attribute]) -> GlobalTestOptions {
  220     use rustc_ast_pretty::pprust;
  221 
  222     let mut opts = GlobalTestOptions { no_crate_inject: false, attrs: Vec::new() };
  223 
  224     let test_attrs: Vec<_> = attrs
  225         .iter()
  226         .filter(|a| a.has_name(sym::doc))
  227         .flat_map(|a| a.meta_item_list().unwrap_or_else(Vec::new))
  228         .filter(|a| a.has_name(sym::test))
  229         .collect();
  230     let attrs = test_attrs.iter().flat_map(|a| a.meta_item_list().unwrap_or(&[]));
  231 
  232     for attr in attrs {
  233         if attr.has_name(sym::no_crate_inject) {
  234             opts.no_crate_inject = true;
  235         }
  236         if attr.has_name(sym::attr) {
  237             if let Some(l) = attr.meta_item_list() {
  238                 for item in l {
  239                     opts.attrs.push(pprust::meta_list_item_to_string(item));
  240                 }
  241             }
  242         }
  243     }
  244 
  245     opts
  246 }
  247 
  248 /// Documentation test failure modes.
  249 enum TestFailure {
  250     /// The test failed to compile.
  251     CompileError,
  252     /// The test is marked `compile_fail` but compiled successfully.
  253     UnexpectedCompilePass,
  254     /// The test failed to compile (as expected) but the compiler output did not contain all
  255     /// expected error codes.
  256     MissingErrorCodes(Vec<String>),
  257     /// The test binary was unable to be executed.
  258     ExecutionError(io::Error),
  259     /// The test binary exited with a non-zero exit code.
  260     ///
  261     /// This typically means an assertion in the test failed or another form of panic occurred.
  262     ExecutionFailure(process::Output),
  263     /// The test is marked `should_panic` but the test binary executed successfully.
  264     UnexpectedRunPass,
  265 }
  266 
  267 enum DirState {
  268     Temp(tempfile::TempDir),
  269     Perm(PathBuf),
  270 }
  271 
  272 impl DirState {
  273     fn path(&self) -> &std::path::Path {
  274         match self {
  275             DirState::Temp(t) => t.path(),
  276             DirState::Perm(p) => p.as_path(),
  277         }
  278     }
  279 }
  280 
  281 // NOTE: Keep this in sync with the equivalent structs in rustc
  282 // and cargo.
  283 // We could unify this struct the one in rustc but they have different
  284 // ownership semantics, so doing so would create wasteful allocations.
  285 #[derive(serde::Serialize, serde::Deserialize)]
  286 struct UnusedExterns {
  287     /// Lint level of the unused_crate_dependencies lint
  288     lint_level: String,
  289     /// List of unused externs by their names.
  290     unused_extern_names: Vec<String>,
  291 }
  292 
  293 fn run_test(
  294     test: &str,
  295     crate_name: &str,
  296     line: usize,
  297     rustdoc_options: RustdocOptions,
  298     mut lang_string: LangString,
  299     no_run: bool,
  300     runtool: Option<String>,
  301     runtool_args: Vec<String>,
  302     target: TargetTriple,
  303     opts: &GlobalTestOptions,
  304     edition: Edition,
  305     outdir: DirState,
  306     path: PathBuf,
  307     test_id: &str,
  308     report_unused_externs: impl Fn(UnusedExterns),
  309 ) -> Result<(), TestFailure> {
  310     let (test, line_offset, supports_color) =
  311         make_test(test, Some(crate_name), lang_string.test_harness, opts, edition, Some(test_id));
  312 
  313     let output_file = outdir.path().join("rust_out");
  314 
  315     let rustc_binary = rustdoc_options
  316         .test_builder
  317         .as_deref()
  318         .unwrap_or_else(|| rustc_interface::util::rustc_path().expect("found rustc"));
  319     let mut compiler = Command::new(&rustc_binary);
  320     compiler.arg("--crate-type").arg("bin");
  321     for cfg in &rustdoc_options.cfgs {
  322         compiler.arg("--cfg").arg(&cfg);
  323     }
  324     if !rustdoc_options.check_cfgs.is_empty() {
  325         compiler.arg("-Z").arg("unstable-options");
  326         for check_cfg in &rustdoc_options.check_cfgs {
  327             compiler.arg("--check-cfg").arg(&check_cfg);
  328         }
  329     }
  330     if let Some(sysroot) = rustdoc_options.maybe_sysroot {
  331         compiler.arg("--sysroot").arg(sysroot);
  332     }
  333     compiler.arg("--edition").arg(&edition.to_string());
  334     compiler.env("UNSTABLE_RUSTDOC_TEST_PATH", path);
  335     compiler.env("UNSTABLE_RUSTDOC_TEST_LINE", format!("{}", line as isize - line_offset as isize));
  336     compiler.arg("-o").arg(&output_file);
  337     if lang_string.test_harness {
  338         compiler.arg("--test");
  339     }
  340     if rustdoc_options.json_unused_externs && !lang_string.compile_fail {
  341         compiler.arg("--error-format=json");
  342         compiler.arg("--json").arg("unused-externs");
  343         compiler.arg("-Z").arg("unstable-options");
  344         compiler.arg("-W").arg("unused_crate_dependencies");
  345     }
  346     for lib_str in &rustdoc_options.lib_strs {
  347         compiler.arg("-L").arg(&lib_str);
  348     }
  349     for extern_str in &rustdoc_options.extern_strs {
  350         compiler.arg("--extern").arg(&extern_str);
  351     }
  352     compiler.arg("-Ccodegen-units=1");
  353     for codegen_options_str in &rustdoc_options.codegen_options_strs {
  354         compiler.arg("-C").arg(&codegen_options_str);
  355     }
  356     for debugging_option_str in &rustdoc_options.debugging_opts_strs {
  357         compiler.arg("-Z").arg(&debugging_option_str);
  358     }
  359     if no_run && !lang_string.compile_fail && rustdoc_options.persist_doctests.is_none() {
  360         compiler.arg("--emit=metadata");
  361     }
  362     compiler.arg("--target").arg(match target {
  363         TargetTriple::TargetTriple(s) => s,
  364         TargetTriple::TargetPath(path) => {
  365             path.to_str().expect("target path must be valid unicode").to_string()
  366         }
  367     });
  368     if let ErrorOutputType::HumanReadable(kind) = rustdoc_options.error_format {
  369         let (short, color_config) = kind.unzip();
  370 
  371         if short {
  372             compiler.arg("--error-format").arg("short");
  373         }
  374 
  375         match color_config {
  376             ColorConfig::Never => {
  377                 compiler.arg("--color").arg("never");
  378             }
  379             ColorConfig::Always => {
  380                 compiler.arg("--color").arg("always");
  381             }
  382             ColorConfig::Auto => {
  383                 compiler.arg("--color").arg(if supports_color { "always" } else { "never" });
  384             }
  385         }
  386     }
  387 
  388     compiler.arg("-");
  389     compiler.stdin(Stdio::piped());
  390     compiler.stderr(Stdio::piped());
  391 
  392     let mut child = compiler.spawn().expect("Failed to spawn rustc process");
  393     {
  394         let stdin = child.stdin.as_mut().expect("Failed to open stdin");
  395         stdin.write_all(test.as_bytes()).expect("could write out test sources");
  396     }
  397     let output = child.wait_with_output().expect("Failed to read stdout");
  398 
  399     struct Bomb<'a>(&'a str);
  400     impl Drop for Bomb<'_> {
  401         fn drop(&mut self) {
  402             eprint!("{}", self.0);
  403         }
  404     }
  405     let mut out = str::from_utf8(&output.stderr)
  406         .unwrap()
  407         .lines()
  408         .filter(|l| {
  409             if let Ok(uext) = serde_json::from_str::<UnusedExterns>(l) {
  410                 report_unused_externs(uext);
  411                 false
  412             } else {
  413                 true
  414             }
  415         })
  416         .intersperse_with(|| "\n")
  417         .collect::<String>();
  418 
  419     // Add a \n to the end to properly terminate the last line,
  420     // but only if there was output to be printed
  421     if !out.is_empty() {
  422         out.push('\n');
  423     }
  424 
  425     let _bomb = Bomb(&out);
  426     match (output.status.success(), lang_string.compile_fail) {
  427         (true, true) => {
  428             return Err(TestFailure::UnexpectedCompilePass);
  429         }
  430         (true, false) => {}
  431         (false, true) => {
  432             if !lang_string.error_codes.is_empty() {
  433                 // We used to check if the output contained "error[{}]: " but since we added the
  434                 // colored output, we can't anymore because of the color escape characters before
  435                 // the ":".
  436                 lang_string.error_codes.retain(|err| !out.contains(&format!("error[{err}]")));
  437 
  438                 if !lang_string.error_codes.is_empty() {
  439                     return Err(TestFailure::MissingErrorCodes(lang_string.error_codes));
  440                 }
  441             }
  442         }
  443         (false, false) => {
  444             return Err(TestFailure::CompileError);
  445         }
  446     }
  447 
  448     if no_run {
  449         return Ok(());
  450     }
  451 
  452     // Run the code!
  453     let mut cmd;
  454 
  455     if let Some(tool) = runtool {
  456         cmd = Command::new(tool);
  457         cmd.args(runtool_args);
  458         cmd.arg(output_file);
  459     } else {
  460         cmd = Command::new(output_file);
  461     }
  462     if let Some(run_directory) = rustdoc_options.test_run_directory {
  463         cmd.current_dir(run_directory);
  464     }
  465 
  466     let result = if rustdoc_options.nocapture {
  467         cmd.status().map(|status| process::Output {
  468             status,
  469             stdout: Vec::new(),
  470             stderr: Vec::new(),
  471         })
  472     } else {
  473         cmd.output()
  474     };
  475     match result {
  476         Err(e) => return Err(TestFailure::ExecutionError(e)),
  477         Ok(out) => {
  478             if lang_string.should_panic && out.status.success() {
  479                 return Err(TestFailure::UnexpectedRunPass);
  480             } else if !lang_string.should_panic && !out.status.success() {
  481                 return Err(TestFailure::ExecutionFailure(out));
  482             }
  483         }
  484     }
  485 
  486     Ok(())
  487 }
  488 
  489 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
  490 /// lines before the test code begins as well as if the output stream supports colors or not.
  491 crate fn make_test(
  492     s: &str,
  493     crate_name: Option<&str>,
  494     dont_insert_main: bool,
  495     opts: &GlobalTestOptions,
  496     edition: Edition,
  497     test_id: Option<&str>,
  498 ) -> (String, usize, bool) {
  499     let (crate_attrs, everything_else, crates) = partition_source(s, edition);
  500     let everything_else = everything_else.trim();
  501     let mut line_offset = 0;
  502     let mut prog = String::new();
  503     let mut supports_color = false;
  504 
  505     if opts.attrs.is_empty() {
  506         // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
  507         // lints that are commonly triggered in doctests. The crate-level test attributes are
  508         // commonly used to make tests fail in case they trigger warnings, so having this there in
  509         // that case may cause some tests to pass when they shouldn't have.
  510         prog.push_str("#![allow(unused)]\n");
  511         line_offset += 1;
  512     }
  513 
  514     // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
  515     for attr in &opts.attrs {
  516         prog.push_str(&format!("#![{attr}]\n"));
  517         line_offset += 1;
  518     }
  519 
  520     // Now push any outer attributes from the example, assuming they
  521     // are intended to be crate attributes.
  522     prog.push_str(&crate_attrs);
  523     prog.push_str(&crates);
  524 
  525     // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
  526     // crate already is included.
  527     let result = rustc_driver::catch_fatal_errors(|| {
  528         rustc_span::create_session_if_not_set_then(edition, |_| {
  529             use rustc_errors::emitter::{Emitter, EmitterWriter};
  530             use rustc_errors::Handler;
  531             use rustc_parse::parser::ForceCollect;
  532             use rustc_span::source_map::FilePathMapping;
  533 
  534             let filename = FileName::anon_source_code(s);
  535             let source = crates + everything_else;
  536 
  537             // Any errors in parsing should also appear when the doctest is compiled for real, so just
  538             // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
  539             let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
  540             supports_color =
  541                 EmitterWriter::stderr(ColorConfig::Auto, None, false, false, Some(80), false)
  542                     .supports_color();
  543 
  544             let emitter =
  545                 EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
  546 
  547             // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
  548             let handler = Handler::with_emitter(false, None, box emitter);
  549             let sess = ParseSess::with_span_handler(handler, sm);
  550 
  551             let mut found_main = false;
  552             let mut found_extern_crate = crate_name.is_none();
  553             let mut found_macro = false;
  554 
  555             let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source) {
  556                 Ok(p) => p,
  557                 Err(errs) => {
  558                     drop(errs);
  559                     return (found_main, found_extern_crate, found_macro);
  560                 }
  561             };
  562 
  563             loop {
  564                 match parser.parse_item(ForceCollect::No) {
  565                     Ok(Some(item)) => {
  566                         if !found_main {
  567                             if let ast::ItemKind::Fn(..) = item.kind {
  568                                 if item.ident.name == sym::main {
  569                                     found_main = true;
  570                                 }
  571                             }
  572                         }
  573 
  574                         if !found_extern_crate {
  575                             if let ast::ItemKind::ExternCrate(original) = item.kind {
  576                                 // This code will never be reached if `crate_name` is none because
  577                                 // `found_extern_crate` is initialized to `true` if it is none.
  578                                 let crate_name = crate_name.unwrap();
  579 
  580                                 match original {
  581                                     Some(name) => found_extern_crate = name.as_str() == crate_name,
  582                                     None => found_extern_crate = item.ident.as_str() == crate_name,
  583                                 }
  584                             }
  585                         }
  586 
  587                         if !found_macro {
  588                             if let ast::ItemKind::MacCall(..) = item.kind {
  589                                 found_macro = true;
  590                             }
  591                         }
  592 
  593                         if found_main && found_extern_crate {
  594                             break;
  595                         }
  596                     }
  597                     Ok(None) => break,
  598                     Err(e) => {
  599                         e.cancel();
  600                         break;
  601                     }
  602                 }
  603 
  604                 // The supplied slice is only used for diagnostics,
  605                 // which are swallowed here anyway.
  606                 parser.maybe_consume_incorrect_semicolon(&[]);
  607             }
  608 
  609             // Reset errors so that they won't be reported as compiler bugs when dropping the
  610             // handler. Any errors in the tests will be reported when the test file is compiled,
  611             // Note that we still need to cancel the errors above otherwise `DiagnosticBuilder`
  612             // will panic on drop.
  613             sess.span_diagnostic.reset_err_count();
  614 
  615             (found_main, found_extern_crate, found_macro)
  616         })
  617     });
  618     let Ok((already_has_main, already_has_extern_crate, found_macro)) = result
  619     else {
  620         // If the parser panicked due to a fatal error, pass the test code through unchanged.
  621         // The error will be reported during compilation.
  622         return (s.to_owned(), 0, false);
  623     };
  624 
  625     // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
  626     // see it. In that case, run the old text-based scan to see if they at least have a main
  627     // function written inside a macro invocation. See
  628     // https://github.com/rust-lang/rust/issues/56898
  629     let already_has_main = if found_macro && !already_has_main {
  630         s.lines()
  631             .map(|line| {
  632                 let comment = line.find("//");
  633                 if let Some(comment_begins) = comment { &line[0..comment_begins] } else { line }
  634             })
  635             .any(|code| code.contains("fn main"))
  636     } else {
  637         already_has_main
  638     };
  639 
  640     // Don't inject `extern crate std` because it's already injected by the
  641     // compiler.
  642     if !already_has_extern_crate && !opts.no_crate_inject && crate_name != Some("std") {
  643         if let Some(crate_name) = crate_name {
  644             // Don't inject `extern crate` if the crate is never used.
  645             // NOTE: this is terribly inaccurate because it doesn't actually
  646             // parse the source, but only has false positives, not false
  647             // negatives.
  648             if s.contains(crate_name) {
  649                 prog.push_str(&format!("extern crate r#{crate_name};\n"));
  650                 line_offset += 1;
  651             }
  652         }
  653     }
  654 
  655     // FIXME: This code cannot yet handle no_std test cases yet
  656     if dont_insert_main || already_has_main || prog.contains("![no_std]") {
  657         prog.push_str(everything_else);
  658     } else {
  659         let returns_result = everything_else.trim_end().ends_with("(())");
  660         // Give each doctest main function a unique name.
  661         // This is for example needed for the tooling around `-C instrument-coverage`.
  662         let inner_fn_name = if let Some(test_id) = test_id {
  663             format!("_doctest_main_{test_id}")
  664         } else {
  665             "_inner".into()
  666         };
  667         let inner_attr = if test_id.is_some() { "#[allow(non_snake_case)] " } else { "" };
  668         let (main_pre, main_post) = if returns_result {
  669             (
  670                 format!(
  671                     "fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n",
  672                 ),
  673                 format!("\n}} {inner_fn_name}().unwrap() }}"),
  674             )
  675         } else if test_id.is_some() {
  676             (
  677                 format!("fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n",),
  678                 format!("\n}} {inner_fn_name}() }}"),
  679             )
  680         } else {
  681             ("fn main() {\n".into(), "\n}".into())
  682         };
  683         // Note on newlines: We insert a line/newline *before*, and *after*
  684         // the doctest and adjust the `line_offset` accordingly.
  685         // In the case of `-C instrument-coverage`, this means that the generated
  686         // inner `main` function spans from the doctest opening codeblock to the
  687         // closing one. For example
  688         // /// ``` <- start of the inner main
  689         // /// <- code under doctest
  690         // /// ``` <- end of the inner main
  691         line_offset += 1;
  692 
  693         prog.extend([&main_pre, everything_else, &main_post].iter().cloned());
  694     }
  695 
  696     debug!("final doctest:\n{prog}");
  697 
  698     (prog, line_offset, supports_color)
  699 }
  700 
  701 fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
  702     if source.is_empty() {
  703         // Empty content so nothing to check in here...
  704         return true;
  705     }
  706     rustc_span::create_session_if_not_set_then(edition, |_| {
  707         let filename = FileName::anon_source_code(source);
  708         let sess = ParseSess::with_silent_emitter(None);
  709         let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned())
  710         {
  711             Ok(p) => p,
  712             Err(_) => {
  713                 debug!("Cannot build a parser to check mod attr so skipping...");
  714                 return true;
  715             }
  716         };
  717         // If a parsing error happened, it's very likely that the attribute is incomplete.
  718         if !parser.parse_attribute(InnerAttrPolicy::Permitted).is_ok() {
  719             return false;
  720         }
  721         // We now check if there is an unclosed delimiter for the attribute. To do so, we look at
  722         // the `unclosed_delims` and see if the opening square bracket was closed.
  723         parser
  724             .unclosed_delims()
  725             .get(0)
  726             .map(|unclosed| {
  727                 unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2)
  728             })
  729             .unwrap_or(true)
  730     })
  731 }
  732 
  733 fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
  734     #[derive(Copy, Clone, PartialEq)]
  735     enum PartitionState {
  736         Attrs,
  737         Crates,
  738         Other,
  739     }
  740     let mut state = PartitionState::Attrs;
  741     let mut before = String::new();
  742     let mut crates = String::new();
  743     let mut after = String::new();
  744 
  745     let mut mod_attr_pending = String::new();
  746 
  747     for line in s.lines() {
  748         let trimline = line.trim();
  749 
  750         // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
  751         // shunted into "everything else"
  752         match state {
  753             PartitionState::Attrs => {
  754                 state = if trimline.starts_with("#![") {
  755                     if !check_if_attr_is_complete(line, edition) {
  756                         mod_attr_pending = line.to_owned();
  757                     } else {
  758                         mod_attr_pending.clear();
  759                     }
  760                     PartitionState::Attrs
  761                 } else if trimline.chars().all(|c| c.is_whitespace())
  762                     || (trimline.starts_with("//") && !trimline.starts_with("///"))
  763                 {
  764                     PartitionState::Attrs
  765                 } else if trimline.starts_with("extern crate")
  766                     || trimline.starts_with("#[macro_use] extern crate")
  767                 {
  768                     PartitionState::Crates
  769                 } else {
  770                     // First we check if the previous attribute was "complete"...
  771                     if !mod_attr_pending.is_empty() {
  772                         // If not, then we append the new line into the pending attribute to check
  773                         // if this time it's complete...
  774                         mod_attr_pending.push_str(line);
  775                         if !trimline.is_empty() && check_if_attr_is_complete(line, edition) {
  776                             // If it's complete, then we can clear the pending content.
  777                             mod_attr_pending.clear();
  778                         }
  779                         // In any case, this is considered as `PartitionState::Attrs` so it's
  780                         // prepended before rustdoc's inserts.
  781                         PartitionState::Attrs
  782                     } else {
  783                         PartitionState::Other
  784                     }
  785                 };
  786             }
  787             PartitionState::Crates => {
  788                 state = if trimline.starts_with("extern crate")
  789                     || trimline.starts_with("#[macro_use] extern crate")
  790                     || trimline.chars().all(|c| c.is_whitespace())
  791                     || (trimline.starts_with("//") && !trimline.starts_with("///"))
  792                 {
  793                     PartitionState::Crates
  794                 } else {
  795                     PartitionState::Other
  796                 };
  797             }
  798             PartitionState::Other => {}
  799         }
  800 
  801         match state {
  802             PartitionState::Attrs => {
  803                 before.push_str(line);
  804                 before.push('\n');
  805             }
  806             PartitionState::Crates => {
  807                 crates.push_str(line);
  808                 crates.push('\n');
  809             }
  810             PartitionState::Other => {
  811                 after.push_str(line);
  812                 after.push('\n');
  813             }
  814         }
  815     }
  816 
  817     debug!("before:\n{before}");
  818     debug!("crates:\n{crates}");
  819     debug!("after:\n{after}");
  820 
  821     (before, after, crates)
  822 }
  823 
  824 crate trait Tester {
  825     fn add_test(&mut self, test: String, config: LangString, line: usize);
  826     fn get_line(&self) -> usize {
  827         0
  828     }
  829     fn register_header(&mut self, _name: &str, _level: u32) {}
  830 }
  831 
  832 crate struct Collector {
  833     crate tests: Vec<test::TestDescAndFn>,
  834 
  835     // The name of the test displayed to the user, separated by `::`.
  836     //
  837     // In tests from Rust source, this is the path to the item
  838     // e.g., `["std", "vec", "Vec", "push"]`.
  839     //
  840     // In tests from a markdown file, this is the titles of all headers (h1~h6)
  841     // of the sections that contain the code block, e.g., if the markdown file is
  842     // written as:
  843     //
  844     // ``````markdown
  845     // # Title
  846     //
  847     // ## Subtitle
  848     //
  849     // ```rust
  850     // assert!(true);
  851     // ```
  852     // ``````
  853     //
  854     // the `names` vector of that test will be `["Title", "Subtitle"]`.
  855     names: Vec<String>,
  856 
  857     rustdoc_options: RustdocOptions,
  858     use_headers: bool,
  859     enable_per_target_ignores: bool,
  860     crate_name: Symbol,
  861     opts: GlobalTestOptions,
  862     position: Span,
  863     source_map: Option<Lrc<SourceMap>>,
  864     filename: Option<PathBuf>,
  865     visited_tests: FxHashMap<(String, usize), usize>,
  866     unused_extern_reports: Arc<Mutex<Vec<UnusedExterns>>>,
  867     compiling_test_count: AtomicUsize,
  868 }
  869 
  870 impl Collector {
  871     crate fn new(
  872         crate_name: Symbol,
  873         rustdoc_options: RustdocOptions,
  874         use_headers: bool,
  875         opts: GlobalTestOptions,
  876         source_map: Option<Lrc<SourceMap>>,
  877         filename: Option<PathBuf>,
  878         enable_per_target_ignores: bool,
  879     ) -> Collector {
  880         Collector {
  881             tests: Vec::new(),
  882             names: Vec::new(),
  883             rustdoc_options,
  884             use_headers,
  885             enable_per_target_ignores,
  886             crate_name,
  887             opts,
  888             position: DUMMY_SP,
  889             source_map,
  890             filename,
  891             visited_tests: FxHashMap::default(),
  892             unused_extern_reports: Default::default(),
  893             compiling_test_count: AtomicUsize::new(0),
  894         }
  895     }
  896 
  897     fn generate_name(&self, line: usize, filename: &FileName) -> String {
  898         let mut item_path = self.names.join("::");
  899         item_path.retain(|c| c != ' ');
  900         if !item_path.is_empty() {
  901             item_path.push(' ');
  902         }
  903         format!("{} - {}(line {})", filename.prefer_local(), item_path, line)
  904     }
  905 
  906     crate fn set_position(&mut self, position: Span) {
  907         self.position = position;
  908     }
  909 
  910     fn get_filename(&self) -> FileName {
  911         if let Some(ref source_map) = self.source_map {
  912             let filename = source_map.span_to_filename(self.position);
  913             if let FileName::Real(ref filename) = filename {
  914                 if let Ok(cur_dir) = env::current_dir() {
  915                     if let Some(local_path) = filename.local_path() {
  916                         if let Ok(path) = local_path.strip_prefix(&cur_dir) {
  917                             return path.to_owned().into();
  918                         }
  919                     }
  920                 }
  921             }
  922             filename
  923         } else if let Some(ref filename) = self.filename {
  924             filename.clone().into()
  925         } else {
  926             FileName::Custom("input".to_owned())
  927         }
  928     }
  929 }
  930 
  931 impl Tester for Collector {
  932     fn add_test(&mut self, test: String, config: LangString, line: usize) {
  933         let filename = self.get_filename();
  934         let name = self.generate_name(line, &filename);
  935         let crate_name = self.crate_name.to_string();
  936         let opts = self.opts.clone();
  937         let edition = config.edition.unwrap_or(self.rustdoc_options.edition);
  938         let rustdoc_options = self.rustdoc_options.clone();
  939         let runtool = self.rustdoc_options.runtool.clone();
  940         let runtool_args = self.rustdoc_options.runtool_args.clone();
  941         let target = self.rustdoc_options.target.clone();
  942         let target_str = target.to_string();
  943         let unused_externs = self.unused_extern_reports.clone();
  944         let no_run = config.no_run || rustdoc_options.no_run;
  945         if !config.compile_fail {
  946             self.compiling_test_count.fetch_add(1, Ordering::SeqCst);
  947         }
  948 
  949         let path = match &filename {
  950             FileName::Real(path) => {
  951                 if let Some(local_path) = path.local_path() {
  952                     local_path.to_path_buf()
  953                 } else {
  954                     // Somehow we got the filename from the metadata of another crate, should never happen
  955                     unreachable!("doctest from a different crate");
  956                 }
  957             }
  958             _ => PathBuf::from(r"doctest.rs"),
  959         };
  960 
  961         // For example `module/file.rs` would become `module_file_rs`
  962         let file = filename
  963             .prefer_local()
  964             .to_string_lossy()
  965             .chars()
  966             .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
  967             .collect::<String>();
  968         let test_id = format!(
  969             "{file}_{line}_{number}",
  970             file = file,
  971             line = line,
  972             number = {
  973                 // Increases the current test number, if this file already
  974                 // exists or it creates a new entry with a test number of 0.
  975                 self.visited_tests.entry((file.clone(), line)).and_modify(|v| *v += 1).or_insert(0)
  976             },
  977         );
  978         let outdir = if let Some(mut path) = rustdoc_options.persist_doctests.clone() {
  979             path.push(&test_id);
  980 
  981             std::fs::create_dir_all(&path)
  982                 .expect("Couldn't create directory for doctest executables");
  983 
  984             DirState::Perm(path)
  985         } else {
  986             DirState::Temp(
  987                 TempFileBuilder::new()
  988                     .prefix("rustdoctest")
  989                     .tempdir()
  990                     .expect("rustdoc needs a tempdir"),
  991             )
  992         };
  993 
  994         debug!("creating test {name}: {test}");
  995         self.tests.push(test::TestDescAndFn {
  996             desc: test::TestDesc {
  997                 name: test::DynTestName(name),
  998                 ignore: match config.ignore {
  999                     Ignore::All => true,
 1000                     Ignore::None => false,
 1001                     Ignore::Some(ref ignores) => ignores.iter().any(|s| target_str.contains(s)),
 1002                 },
 1003                 #[cfg(not(bootstrap))]
 1004                 ignore_message: None,
 1005                 // compiler failures are test failures
 1006                 should_panic: test::ShouldPanic::No,
 1007                 compile_fail: config.compile_fail,
 1008                 no_run,
 1009                 test_type: test::TestType::DocTest,
 1010             },
 1011             testfn: test::DynTestFn(box move || {
 1012                 let report_unused_externs = |uext| {
 1013                     unused_externs.lock().unwrap().push(uext);
 1014                 };
 1015                 let res = run_test(
 1016                     &test,
 1017                     &crate_name,
 1018                     line,
 1019                     rustdoc_options,
 1020                     config,
 1021                     no_run,
 1022                     runtool,
 1023                     runtool_args,
 1024                     target,
 1025                     &opts,
 1026                     edition,
 1027                     outdir,
 1028                     path,
 1029                     &test_id,
 1030                     report_unused_externs,
 1031                 );
 1032 
 1033                 if let Err(err) = res {
 1034                     match err {
 1035                         TestFailure::CompileError => {
 1036                             eprint!("Couldn't compile the test.");
 1037                         }
 1038                         TestFailure::UnexpectedCompilePass => {
 1039                             eprint!("Test compiled successfully, but it's marked `compile_fail`.");
 1040                         }
 1041                         TestFailure::UnexpectedRunPass => {
 1042                             eprint!("Test executable succeeded, but it's marked `should_panic`.");
 1043                         }
 1044                         TestFailure::MissingErrorCodes(codes) => {
 1045                             eprint!("Some expected error codes were not found: {:?}", codes);
 1046                         }
 1047                         TestFailure::ExecutionError(err) => {
 1048                             eprint!("Couldn't run the test: {err}");
 1049                             if err.kind() == io::ErrorKind::PermissionDenied {
 1050                                 eprint!(" - maybe your tempdir is mounted with noexec?");
 1051                             }
 1052                         }
 1053                         TestFailure::ExecutionFailure(out) => {
 1054                             let reason = if let Some(code) = out.status.code() {
 1055                                 format!("exit code {code}")
 1056                             } else {
 1057                                 String::from("terminated by signal")
 1058                             };
 1059 
 1060                             eprintln!("Test executable failed ({reason}).");
 1061 
 1062                             // FIXME(#12309): An unfortunate side-effect of capturing the test
 1063                             // executable's output is that the relative ordering between the test's
 1064                             // stdout and stderr is lost. However, this is better than the
 1065                             // alternative: if the test executable inherited the parent's I/O
 1066                             // handles the output wouldn't be captured at all, even on success.
 1067                             //
 1068                             // The ordering could be preserved if the test process' stderr was
 1069                             // redirected to stdout, but that functionality does not exist in the
 1070                             // standard library, so it may not be portable enough.
 1071                             let stdout = str::from_utf8(&out.stdout).unwrap_or_default();
 1072                             let stderr = str::from_utf8(&out.stderr).unwrap_or_default();
 1073 
 1074                             if !stdout.is_empty() || !stderr.is_empty() {
 1075                                 eprintln!();
 1076 
 1077                                 if !stdout.is_empty() {
 1078                                     eprintln!("stdout:\n{stdout}");
 1079                                 }
 1080 
 1081                                 if !stderr.is_empty() {
 1082                                     eprintln!("stderr:\n{stderr}");
 1083                                 }
 1084                             }
 1085                         }
 1086                     }
 1087 
 1088                     panic::resume_unwind(box ());
 1089                 }
 1090             }),
 1091         });
 1092     }
 1093 
 1094     fn get_line(&self) -> usize {
 1095         if let Some(ref source_map) = self.source_map {
 1096             let line = self.position.lo().to_usize();
 1097             let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
 1098             if line > 0 { line - 1 } else { line }
 1099         } else {
 1100             0
 1101         }
 1102     }
 1103 
 1104     fn register_header(&mut self, name: &str, level: u32) {
 1105         if self.use_headers {
 1106             // We use these headings as test names, so it's good if
 1107             // they're valid identifiers.
 1108             let name = name
 1109                 .chars()
 1110                 .enumerate()
 1111                 .map(|(i, c)| {
 1112                     if (i == 0 && rustc_lexer::is_id_start(c))
 1113                         || (i != 0 && rustc_lexer::is_id_continue(c))
 1114                     {
 1115                         c
 1116                     } else {
 1117                         '_'
 1118                     }
 1119                 })
 1120                 .collect::<String>();
 1121 
 1122             // Here we try to efficiently assemble the header titles into the
 1123             // test name in the form of `h1::h2::h3::h4::h5::h6`.
 1124             //
 1125             // Suppose that originally `self.names` contains `[h1, h2, h3]`...
 1126             let level = level as usize;
 1127             if level <= self.names.len() {
 1128                 // ... Consider `level == 2`. All headers in the lower levels
 1129                 // are irrelevant in this new level. So we should reset
 1130                 // `self.names` to contain headers until <h2>, and replace that
 1131                 // slot with the new name: `[h1, name]`.
 1132                 self.names.truncate(level);
 1133                 self.names[level - 1] = name;
 1134             } else {
 1135                 // ... On the other hand, consider `level == 5`. This means we
 1136                 // need to extend `self.names` to contain five headers. We fill
 1137                 // in the missing level (<h4>) with `_`. Thus `self.names` will
 1138                 // become `[h1, h2, h3, "_", name]`.
 1139                 if level - 1 > self.names.len() {
 1140                     self.names.resize(level - 1, "_".to_owned());
 1141                 }
 1142                 self.names.push(name);
 1143             }
 1144         }
 1145     }
 1146 }
 1147 
 1148 struct HirCollector<'a, 'hir, 'tcx> {
 1149     sess: &'a Session,
 1150     collector: &'a mut Collector,
 1151     map: Map<'hir>,
 1152     codes: ErrorCodes,
 1153     tcx: TyCtxt<'tcx>,
 1154 }
 1155 
 1156 impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> {
 1157     fn visit_testable<F: FnOnce(&mut Self)>(
 1158         &mut self,
 1159         name: String,
 1160         hir_id: HirId,
 1161         sp: Span,
 1162         nested: F,
 1163     ) {
 1164         let ast_attrs = self.tcx.hir().attrs(hir_id);
 1165         let mut attrs = Attributes::from_ast(ast_attrs, None);
 1166 
 1167         if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) {
 1168             if !cfg.matches(&self.sess.parse_sess, Some(self.sess.features_untracked())) {
 1169                 return;
 1170             }
 1171         }
 1172 
 1173         let has_name = !name.is_empty();
 1174         if has_name {
 1175             self.collector.names.push(name);
 1176         }
 1177 
 1178         attrs.unindent_doc_comments();
 1179         // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
 1180         // anything else, this will combine them for us.
 1181         if let Some(doc) = attrs.collapsed_doc_value() {
 1182             // Use the outermost invocation, so that doctest names come from where the docs were written.
 1183             let span = ast_attrs
 1184                 .span()
 1185                 .map(|span| span.ctxt().outer_expn().expansion_cause().unwrap_or(span))
 1186                 .unwrap_or(DUMMY_SP);
 1187             self.collector.set_position(span);
 1188             markdown::find_testable_code(
 1189                 &doc,
 1190                 self.collector,
 1191                 self.codes,
 1192                 self.collector.enable_per_target_ignores,
 1193                 Some(&crate::html::markdown::ExtraInfo::new(
 1194                     self.tcx,
 1195                     hir_id,
 1196                     span_of_attrs(&attrs).unwrap_or(sp),
 1197                 )),
 1198             );
 1199         }
 1200 
 1201         nested(self);
 1202 
 1203         if has_name {
 1204             self.collector.names.pop();
 1205         }
 1206     }
 1207 }
 1208 
 1209 impl<'a, 'hir, 'tcx> intravisit::Visitor<'hir> for HirCollector<'a, 'hir, 'tcx> {
 1210     type NestedFilter = nested_filter::All;
 1211 
 1212     fn nested_visit_map(&mut self) -> Self::Map {
 1213         self.map
 1214     }
 1215 
 1216     fn visit_item(&mut self, item: &'hir hir::Item<'_>) {
 1217         let name = match &item.kind {
 1218             hir::ItemKind::Macro(ref macro_def, _) => {
 1219                 // FIXME(#88038): Non exported macros have historically not been tested,
 1220                 // but we really ought to start testing them.
 1221                 let def_id = item.def_id.to_def_id();
 1222                 if macro_def.macro_rules && !self.tcx.has_attr(def_id, sym::macro_export) {
 1223                     intravisit::walk_item(self, item);
 1224                     return;
 1225                 }
 1226                 item.ident.to_string()
 1227             }
 1228             hir::ItemKind::Impl(impl_) => {
 1229                 rustc_hir_pretty::id_to_string(&self.map, impl_.self_ty.hir_id)
 1230             }
 1231             _ => item.ident.to_string(),
 1232         };
 1233 
 1234         self.visit_testable(name, item.hir_id(), item.span, |this| {
 1235             intravisit::walk_item(this, item);
 1236         });
 1237     }
 1238 
 1239     fn visit_trait_item(&mut self, item: &'hir hir::TraitItem<'_>) {
 1240         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
 1241             intravisit::walk_trait_item(this, item);
 1242         });
 1243     }
 1244 
 1245     fn visit_impl_item(&mut self, item: &'hir hir::ImplItem<'_>) {
 1246         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
 1247             intravisit::walk_impl_item(this, item);
 1248         });
 1249     }
 1250 
 1251     fn visit_foreign_item(&mut self, item: &'hir hir::ForeignItem<'_>) {
 1252         self.visit_testable(item.ident.to_string(), item.hir_id(), item.span, |this| {
 1253             intravisit::walk_foreign_item(this, item);
 1254         });
 1255     }
 1256 
 1257     fn visit_variant(
 1258         &mut self,
 1259         v: &'hir hir::Variant<'_>,
 1260         g: &'hir hir::Generics<'_>,
 1261         item_id: hir::HirId,
 1262     ) {
 1263         self.visit_testable(v.ident.to_string(), v.id, v.span, |this| {
 1264             intravisit::walk_variant(this, v, g, item_id);
 1265         });
 1266     }
 1267 
 1268     fn visit_field_def(&mut self, f: &'hir hir::FieldDef<'_>) {
 1269         self.visit_testable(f.ident.to_string(), f.hir_id, f.span, |this| {
 1270             intravisit::walk_field_def(this, f);
 1271         });
 1272     }
 1273 }
 1274 
 1275 #[cfg(test)]
 1276 mod tests;