1 use rustc_data_structures
::sync
::Lrc
;
2 use rustc_feature
::UnstableFeatures
;
3 use rustc_interface
::interface
;
4 use rustc_target
::spec
::TargetTriple
;
6 use rustc
::hir
::intravisit
;
7 use rustc
::session
::{self, config, DiagnosticOutput}
;
8 use rustc
::util
::common
::ErrorReported
;
10 use syntax
::with_globals
;
11 use syntax
::source_map
::SourceMap
;
12 use syntax
::edition
::Edition
;
14 use std
::io
::{self, Write}
;
16 use std
::path
::PathBuf
;
17 use std
::process
::{self, Command, Stdio}
;
19 use syntax
::symbol
::sym
;
20 use syntax_pos
::{BytePos, DUMMY_SP, Pos, Span, FileName}
;
21 use tempfile
::Builder
as TempFileBuilder
;
24 use crate::clean
::Attributes
;
25 use crate::config
::Options
;
26 use crate::html
::markdown
::{self, ErrorCodes, LangString, Ignore}
;
28 #[derive(Clone, Default)]
29 pub struct TestOptions
{
30 /// Whether to disable the default `extern crate my_crate;` when creating doctests.
31 pub no_crate_inject
: bool
,
32 /// Whether to emit compilation warnings when compiling doctests. Setting this will suppress
33 /// the default `#![allow(unused)]`.
34 pub display_warnings
: bool
,
35 /// Additional crate-level attributes to add to doctests.
36 pub attrs
: Vec
<String
>,
39 pub fn run(options
: Options
) -> i32 {
40 let input
= config
::Input
::File(options
.input
.clone());
42 let crate_types
= if options
.proc_macro_crate
{
43 vec
![config
::CrateType
::ProcMacro
]
45 vec
![config
::CrateType
::Dylib
]
48 let sessopts
= config
::Options
{
49 maybe_sysroot
: options
.maybe_sysroot
.clone(),
50 search_paths
: options
.libs
.clone(),
52 cg
: options
.codegen_options
.clone(),
53 externs
: options
.externs
.clone(),
54 unstable_features
: UnstableFeatures
::from_environment(),
55 lint_cap
: Some(::rustc
::lint
::Level
::Allow
),
56 actually_rustdoc
: true,
57 debugging_opts
: config
::DebuggingOptions
{
58 ..config
::basic_debugging_options()
60 edition
: options
.edition
,
61 target_triple
: options
.target
.clone(),
62 ..config
::Options
::default()
65 let mut cfgs
= options
.cfgs
.clone();
66 cfgs
.push("doc".to_owned());
67 cfgs
.push("doctest".to_owned());
68 let config
= interface
::Config
{
70 crate_cfg
: interface
::parse_cfgspecs(cfgs
),
76 diagnostic_output
: DiagnosticOutput
::Default
,
78 crate_name
: options
.crate_name
.clone(),
79 lint_caps
: Default
::default(),
81 override_queries
: None
,
82 registry
: rustc_driver
::diagnostics_registry(),
85 let mut test_args
= options
.test_args
.clone();
86 let display_warnings
= options
.display_warnings
;
88 let tests
= interface
::run_compiler(config
, |compiler
| compiler
.enter(|queries
| {
89 let lower_to_hir
= queries
.lower_to_hir()?
;
91 let mut opts
= scrape_test_config(lower_to_hir
.peek().0.krate());
92 opts
.display_warnings
|= options
.display_warnings
;
93 let enable_per_target_ignores
= options
.enable_per_target_ignores
;
94 let mut collector
= Collector
::new(
95 queries
.crate_name()?
.peek().to_string(),
99 Some(compiler
.source_map().clone()),
101 enable_per_target_ignores
,
104 let mut global_ctxt
= queries
.global_ctxt()?
.take();
106 global_ctxt
.enter(|tcx
| {
107 let krate
= tcx
.hir().krate();
108 let mut hir_collector
= HirCollector
{
109 sess
: compiler
.session(),
110 collector
: &mut collector
,
112 codes
: ErrorCodes
::from(compiler
.session().opts
113 .unstable_features
.is_nightly_build()),
115 hir_collector
.visit_testable("".to_string(), &krate
.attrs
, |this
| {
116 intravisit
::walk_crate(this
, krate
);
120 let ret
: Result
<_
, ErrorReported
> = Ok(collector
.tests
);
122 })).expect("compiler aborted in rustdoc!");
124 test_args
.insert(0, "rustdoctest".to_string());
129 Some(testing
::Options
::new().display_output(display_warnings
))
135 // Look for `#![doc(test(no_crate_inject))]`, used by crates in the std facade.
136 fn scrape_test_config(krate
: &::rustc
::hir
::Crate
) -> TestOptions
{
137 use syntax
::print
::pprust
;
139 let mut opts
= TestOptions
{
140 no_crate_inject
: false,
141 display_warnings
: false,
145 let test_attrs
: Vec
<_
> = krate
.attrs
.iter()
146 .filter(|a
| a
.check_name(sym
::doc
))
147 .flat_map(|a
| a
.meta_item_list().unwrap_or_else(Vec
::new
))
148 .filter(|a
| a
.check_name(sym
::test
))
150 let attrs
= test_attrs
.iter().flat_map(|a
| a
.meta_item_list().unwrap_or(&[]));
153 if attr
.check_name(sym
::no_crate_inject
) {
154 opts
.no_crate_inject
= true;
156 if attr
.check_name(sym
::attr
) {
157 if let Some(l
) = attr
.meta_item_list() {
159 opts
.attrs
.push(pprust
::meta_list_item_to_string(item
));
168 /// Documentation test failure modes.
170 /// The test failed to compile.
172 /// The test is marked `compile_fail` but compiled successfully.
173 UnexpectedCompilePass
,
174 /// The test failed to compile (as expected) but the compiler output did not contain all
175 /// expected error codes.
176 MissingErrorCodes(Vec
<String
>),
177 /// The test binary was unable to be executed.
178 ExecutionError(io
::Error
),
179 /// The test binary exited with a non-zero exit code.
181 /// This typically means an assertion in the test failed or another form of panic occurred.
182 ExecutionFailure(process
::Output
),
183 /// The test is marked `should_panic` but the test binary executed successfully.
195 as_test_harness
: bool
,
196 runtool
: Option
<String
>,
197 runtool_args
: Vec
<String
>,
198 target
: TargetTriple
,
200 mut error_codes
: Vec
<String
>,
203 ) -> Result
<(), TestFailure
> {
204 let (test
, line_offset
) = match panic
::catch_unwind(|| {
205 make_test(test
, Some(cratename
), as_test_harness
, opts
, edition
)
207 Ok((test
, line_offset
)) => (test
, line_offset
),
208 Err(cause
) if cause
.is
::<errors
::FatalErrorMarker
>() => {
209 // If the parser used by `make_test` panicked due to a fatal error, pass the test code
210 // through unchanged. The error will be reported during compilation.
213 Err(cause
) => panic
::resume_unwind(cause
),
216 // FIXME(#44940): if doctests ever support path remapping, then this filename
217 // needs to be the result of `SourceMap::span_to_unmapped_path`.
218 let path
= match filename
{
219 FileName
::Real(path
) => path
.clone(),
220 _
=> PathBuf
::from(r
"doctest.rs"),
224 Temp(tempfile
::TempDir
),
229 fn path(&self) -> &std
::path
::Path
{
231 DirState
::Temp(t
) => t
.path(),
232 DirState
::Perm(p
) => p
.as_path(),
237 let outdir
= if let Some(mut path
) = options
.persist_doctests
{
238 path
.push(format
!("{}_{}",
247 std
::fs
::create_dir_all(&path
)
248 .expect("Couldn't create directory for doctest executables");
252 DirState
::Temp(TempFileBuilder
::new()
253 .prefix("rustdoctest")
255 .expect("rustdoc needs a tempdir"))
257 let output_file
= outdir
.path().join("rust_out");
259 let rustc_binary
= options
.test_builder
.as_ref().map(|v
| &**v
).unwrap_or_else(|| {
260 rustc_interface
::util
::rustc_path().expect("found rustc")
262 let mut compiler
= Command
::new(&rustc_binary
);
263 compiler
.arg("--crate-type").arg("bin");
264 for cfg
in &options
.cfgs
{
265 compiler
.arg("--cfg").arg(&cfg
);
267 if let Some(sysroot
) = options
.maybe_sysroot
{
268 compiler
.arg("--sysroot").arg(sysroot
);
270 compiler
.arg("--edition").arg(&edition
.to_string());
271 compiler
.env("UNSTABLE_RUSTDOC_TEST_PATH", path
);
272 compiler
.env("UNSTABLE_RUSTDOC_TEST_LINE",
273 format
!("{}", line
as isize - line_offset
as isize));
274 compiler
.arg("-o").arg(&output_file
);
276 compiler
.arg("--test");
278 for lib_str
in &options
.lib_strs
{
279 compiler
.arg("-L").arg(&lib_str
);
281 for extern_str
in &options
.extern_strs
{
282 compiler
.arg("--extern").arg(&extern_str
);
284 compiler
.arg("-Ccodegen-units=1");
285 for codegen_options_str
in &options
.codegen_options_strs
{
286 compiler
.arg("-C").arg(&codegen_options_str
);
288 for debugging_option_str
in &options
.debugging_options_strs
{
289 compiler
.arg("-Z").arg(&debugging_option_str
);
292 compiler
.arg("--emit=metadata");
294 compiler
.arg("--target").arg(target
.to_string());
297 compiler
.stdin(Stdio
::piped());
298 compiler
.stderr(Stdio
::piped());
300 let mut child
= compiler
.spawn().expect("Failed to spawn rustc process");
302 let stdin
= child
.stdin
.as_mut().expect("Failed to open stdin");
303 stdin
.write_all(test
.as_bytes()).expect("could write out test sources");
305 let output
= child
.wait_with_output().expect("Failed to read stdout");
307 struct Bomb
<'a
>(&'a
str);
308 impl Drop
for Bomb
<'_
> {
310 eprint
!("{}",self.0);
314 let out
= str::from_utf8(&output
.stderr
).unwrap();
315 let _bomb
= Bomb(&out
);
316 match (output
.status
.success(), compile_fail
) {
318 return Err(TestFailure
::UnexpectedCompilePass
);
322 if !error_codes
.is_empty() {
323 error_codes
.retain(|err
| !out
.contains(err
));
325 if !error_codes
.is_empty() {
326 return Err(TestFailure
::MissingErrorCodes(error_codes
));
331 return Err(TestFailure
::CompileError
);
342 if let Some(tool
) = runtool
{
343 cmd
= Command
::new(tool
);
344 cmd
.arg(output_file
);
345 cmd
.args(runtool_args
);
347 cmd
= Command
::new(output_file
);
351 Err(e
) => return Err(TestFailure
::ExecutionError(e
)),
353 if should_panic
&& out
.status
.success() {
354 return Err(TestFailure
::UnexpectedRunPass
);
355 } else if !should_panic
&& !out
.status
.success() {
356 return Err(TestFailure
::ExecutionFailure(out
));
364 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
365 /// lines before the test code begins.
369 /// This function uses the compiler's parser internally. The parser will panic if it encounters a
370 /// fatal error while parsing the test.
371 pub fn make_test(s
: &str,
372 cratename
: Option
<&str>,
373 dont_insert_main
: bool
,
377 let (crate_attrs
, everything_else
, crates
) = partition_source(s
);
378 let everything_else
= everything_else
.trim();
379 let mut line_offset
= 0;
380 let mut prog
= String
::new();
382 if opts
.attrs
.is_empty() && !opts
.display_warnings
{
383 // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
384 // lints that are commonly triggered in doctests. The crate-level test attributes are
385 // commonly used to make tests fail in case they trigger warnings, so having this there in
386 // that case may cause some tests to pass when they shouldn't have.
387 prog
.push_str("#![allow(unused)]\n");
391 // Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
392 for attr
in &opts
.attrs
{
393 prog
.push_str(&format
!("#![{}]\n", attr
));
397 // Now push any outer attributes from the example, assuming they
398 // are intended to be crate attributes.
399 prog
.push_str(&crate_attrs
);
400 prog
.push_str(&crates
);
402 // Uses libsyntax to parse the doctest and find if there's a main fn and the extern
403 // crate already is included.
404 let (already_has_main
, already_has_extern_crate
, found_macro
) = with_globals(edition
, || {
405 use crate::syntax
::{sess::ParseSess, source_map::FilePathMapping}
;
406 use rustc_parse
::maybe_new_parser_from_source_str
;
407 use errors
::emitter
::EmitterWriter
;
410 let filename
= FileName
::anon_source_code(s
);
411 let source
= crates
+ &everything_else
;
413 // Any errors in parsing should also appear when the doctest is compiled for real, so just
414 // send all the errors that libsyntax emits directly into a `Sink` instead of stderr.
415 let cm
= Lrc
::new(SourceMap
::new(FilePathMapping
::empty()));
416 let emitter
= EmitterWriter
::new(box io
::sink(), None
, false, false, false, None
, false);
417 // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
418 let handler
= Handler
::with_emitter(false, None
, box emitter
);
419 let sess
= ParseSess
::with_span_handler(handler
, cm
);
421 let mut found_main
= false;
422 let mut found_extern_crate
= cratename
.is_none();
423 let mut found_macro
= false;
425 let mut parser
= match maybe_new_parser_from_source_str(&sess
, filename
, source
) {
428 for mut err
in errs
{
432 return (found_main
, found_extern_crate
, found_macro
);
437 match parser
.parse_item() {
440 if let ast
::ItemKind
::Fn(..) = item
.kind
{
441 if item
.ident
.name
== sym
::main
{
447 if !found_extern_crate
{
448 if let ast
::ItemKind
::ExternCrate(original
) = item
.kind
{
449 // This code will never be reached if `cratename` is none because
450 // `found_extern_crate` is initialized to `true` if it is none.
451 let cratename
= cratename
.unwrap();
454 Some(name
) => found_extern_crate
= name
.as_str() == cratename
,
455 None
=> found_extern_crate
= item
.ident
.as_str() == cratename
,
461 if let ast
::ItemKind
::Mac(..) = item
.kind
{
466 if found_main
&& found_extern_crate
{
478 (found_main
, found_extern_crate
, found_macro
)
481 // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
482 // see it. In that case, run the old text-based scan to see if they at least have a main
483 // function written inside a macro invocation. See
484 // https://github.com/rust-lang/rust/issues/56898
485 let already_has_main
= if found_macro
&& !already_has_main
{
488 let comment
= line
.find("//");
489 if let Some(comment_begins
) = comment
{
490 &line
[0..comment_begins
]
495 .any(|code
| code
.contains("fn main"))
500 // Don't inject `extern crate std` because it's already injected by the
502 if !already_has_extern_crate
&& !opts
.no_crate_inject
&& cratename
!= Some("std") {
503 if let Some(cratename
) = cratename
{
504 // Make sure its actually used if not included.
505 if s
.contains(cratename
) {
506 prog
.push_str(&format
!("extern crate {};\n", cratename
));
512 // FIXME: This code cannot yet handle no_std test cases yet
513 if dont_insert_main
|| already_has_main
|| prog
.contains("![no_std]") {
514 prog
.push_str(everything_else
);
516 let returns_result
= everything_else
.trim_end().ends_with("(())");
517 let (main_pre
, main_post
) = if returns_result
{
518 ("fn main() { fn _inner() -> Result<(), impl core::fmt::Debug> {",
519 "}\n_inner().unwrap() }")
521 ("fn main() {\n", "\n}")
523 prog
.extend([main_pre
, everything_else
, main_post
].iter().cloned());
527 debug
!("final doctest:\n{}", prog
);
532 // FIXME(aburka): use a real parser to deal with multiline attributes
533 fn partition_source(s
: &str) -> (String
, String
, String
) {
534 #[derive(Copy, Clone, PartialEq)]
535 enum PartitionState
{
540 let mut state
= PartitionState
::Attrs
;
541 let mut before
= String
::new();
542 let mut crates
= String
::new();
543 let mut after
= String
::new();
545 for line
in s
.lines() {
546 let trimline
= line
.trim();
548 // FIXME(misdreavus): if a doc comment is placed on an extern crate statement, it will be
549 // shunted into "everything else"
551 PartitionState
::Attrs
=> {
552 state
= if trimline
.starts_with("#![") ||
553 trimline
.chars().all(|c
| c
.is_whitespace()) ||
554 (trimline
.starts_with("//") && !trimline
.starts_with("///"))
556 PartitionState
::Attrs
557 } else if trimline
.starts_with("extern crate") ||
558 trimline
.starts_with("#[macro_use] extern crate")
560 PartitionState
::Crates
562 PartitionState
::Other
565 PartitionState
::Crates
=> {
566 state
= if trimline
.starts_with("extern crate") ||
567 trimline
.starts_with("#[macro_use] extern crate") ||
568 trimline
.chars().all(|c
| c
.is_whitespace()) ||
569 (trimline
.starts_with("//") && !trimline
.starts_with("///"))
571 PartitionState
::Crates
573 PartitionState
::Other
576 PartitionState
::Other
=> {}
580 PartitionState
::Attrs
=> {
581 before
.push_str(line
);
582 before
.push_str("\n");
584 PartitionState
::Crates
=> {
585 crates
.push_str(line
);
586 crates
.push_str("\n");
588 PartitionState
::Other
=> {
589 after
.push_str(line
);
590 after
.push_str("\n");
595 debug
!("before:\n{}", before
);
596 debug
!("crates:\n{}", crates
);
597 debug
!("after:\n{}", after
);
599 (before
, after
, crates
)
603 fn add_test(&mut self, test
: String
, config
: LangString
, line
: usize);
604 fn get_line(&self) -> usize {
607 fn register_header(&mut self, _name
: &str, _level
: u32) {}
610 pub struct Collector
{
611 pub tests
: Vec
<testing
::TestDescAndFn
>,
613 // The name of the test displayed to the user, separated by `::`.
615 // In tests from Rust source, this is the path to the item
616 // e.g., `["std", "vec", "Vec", "push"]`.
618 // In tests from a markdown file, this is the titles of all headers (h1~h6)
619 // of the sections that contain the code block, e.g., if the markdown file is
632 // the `names` vector of that test will be `["Title", "Subtitle"]`.
637 enable_per_target_ignores
: bool
,
641 source_map
: Option
<Lrc
<SourceMap
>>,
642 filename
: Option
<PathBuf
>,
646 pub fn new(cratename
: String
, options
: Options
, use_headers
: bool
, opts
: TestOptions
,
647 source_map
: Option
<Lrc
<SourceMap
>>, filename
: Option
<PathBuf
>,
648 enable_per_target_ignores
: bool
) -> Collector
{
654 enable_per_target_ignores
,
663 fn generate_name(&self, line
: usize, filename
: &FileName
) -> String
{
664 format
!("{} - {} (line {})", filename
, self.names
.join("::"), line
)
667 pub fn set_position(&mut self, position
: Span
) {
668 self.position
= position
;
671 fn get_filename(&self) -> FileName
{
672 if let Some(ref source_map
) = self.source_map
{
673 let filename
= source_map
.span_to_filename(self.position
);
674 if let FileName
::Real(ref filename
) = filename
{
675 if let Ok(cur_dir
) = env
::current_dir() {
676 if let Ok(path
) = filename
.strip_prefix(&cur_dir
) {
677 return path
.to_owned().into();
682 } else if let Some(ref filename
) = self.filename
{
683 filename
.clone().into()
685 FileName
::Custom("input".to_owned())
690 impl Tester
for Collector
{
691 fn add_test(&mut self, test
: String
, config
: LangString
, line
: usize) {
692 let filename
= self.get_filename();
693 let name
= self.generate_name(line
, &filename
);
694 let cratename
= self.cratename
.to_string();
695 let opts
= self.opts
.clone();
696 let edition
= config
.edition
.unwrap_or(self.options
.edition
.clone());
697 let options
= self.options
.clone();
698 let runtool
= self.options
.runtool
.clone();
699 let runtool_args
= self.options
.runtool_args
.clone();
700 let target
= self.options
.target
.clone();
701 let target_str
= target
.to_string();
703 debug
!("creating test {}: {}", name
, test
);
704 self.tests
.push(testing
::TestDescAndFn
{
705 desc
: testing
::TestDesc
{
706 name
: testing
::DynTestName(name
.clone()),
707 ignore
: match config
.ignore
{
709 Ignore
::None
=> false,
710 Ignore
::Some(ref ignores
) => {
711 ignores
.iter().any(|s
| target_str
.contains(s
))
714 // compiler failures are test failures
715 should_panic
: testing
::ShouldPanic
::No
,
716 allow_fail
: config
.allow_fail
,
717 test_type
: testing
::TestType
::DocTest
,
719 testfn
: testing
::DynTestFn(box move || {
738 if let Err(err
) = res
{
740 TestFailure
::CompileError
=> {
741 eprint
!("Couldn't compile the test.");
743 TestFailure
::UnexpectedCompilePass
=> {
744 eprint
!("Test compiled successfully, but it's marked `compile_fail`.");
746 TestFailure
::UnexpectedRunPass
=> {
747 eprint
!("Test executable succeeded, but it's marked `should_panic`.");
749 TestFailure
::MissingErrorCodes(codes
) => {
750 eprint
!("Some expected error codes were not found: {:?}", codes
);
752 TestFailure
::ExecutionError(err
) => {
753 eprint
!("Couldn't run the test: {}", err
);
754 if err
.kind() == io
::ErrorKind
::PermissionDenied
{
755 eprint
!(" - maybe your tempdir is mounted with noexec?");
758 TestFailure
::ExecutionFailure(out
) => {
759 let reason
= if let Some(code
) = out
.status
.code() {
760 format
!("exit code {}", code
)
762 String
::from("terminated by signal")
765 eprintln
!("Test executable failed ({}).", reason
);
767 // FIXME(#12309): An unfortunate side-effect of capturing the test
768 // executable's output is that the relative ordering between the test's
769 // stdout and stderr is lost. However, this is better than the
770 // alternative: if the test executable inherited the parent's I/O
771 // handles the output wouldn't be captured at all, even on success.
773 // The ordering could be preserved if the test process' stderr was
774 // redirected to stdout, but that functionality does not exist in the
775 // standard library, so it may not be portable enough.
776 let stdout
= str::from_utf8(&out
.stdout
).unwrap_or_default();
777 let stderr
= str::from_utf8(&out
.stderr
).unwrap_or_default();
779 if !stdout
.is_empty() || !stderr
.is_empty() {
782 if !stdout
.is_empty() {
783 eprintln
!("stdout:\n{}", stdout
);
786 if !stderr
.is_empty() {
787 eprintln
!("stderr:\n{}", stderr
);
793 panic
::resume_unwind(box ());
799 fn get_line(&self) -> usize {
800 if let Some(ref source_map
) = self.source_map
{
801 let line
= self.position
.lo().to_usize();
802 let line
= source_map
.lookup_char_pos(BytePos(line
as u32)).line
;
803 if line
> 0 { line - 1 }
else { line }
809 fn register_header(&mut self, name
: &str, level
: u32) {
810 if self.use_headers
{
811 // We use these headings as test names, so it's good if
812 // they're valid identifiers.
813 let name
= name
.chars().enumerate().map(|(i
, c
)| {
814 if (i
== 0 && rustc_lexer
::is_id_start(c
)) ||
815 (i
!= 0 && rustc_lexer
::is_id_continue(c
)) {
820 }).collect
::<String
>();
822 // Here we try to efficiently assemble the header titles into the
823 // test name in the form of `h1::h2::h3::h4::h5::h6`.
825 // Suppose that originally `self.names` contains `[h1, h2, h3]`...
826 let level
= level
as usize;
827 if level
<= self.names
.len() {
828 // ... Consider `level == 2`. All headers in the lower levels
829 // are irrelevant in this new level. So we should reset
830 // `self.names` to contain headers until <h2>, and replace that
831 // slot with the new name: `[h1, name]`.
832 self.names
.truncate(level
);
833 self.names
[level
- 1] = name
;
835 // ... On the other hand, consider `level == 5`. This means we
836 // need to extend `self.names` to contain five headers. We fill
837 // in the missing level (<h4>) with `_`. Thus `self.names` will
838 // become `[h1, h2, h3, "_", name]`.
839 if level
- 1 > self.names
.len() {
840 self.names
.resize(level
- 1, "_".to_owned());
842 self.names
.push(name
);
848 struct HirCollector
<'a
, 'hir
> {
849 sess
: &'a session
::Session
,
850 collector
: &'a
mut Collector
,
851 map
: &'a hir
::map
::Map
<'hir
>,
855 impl<'a
, 'hir
> HirCollector
<'a
, 'hir
> {
856 fn visit_testable
<F
: FnOnce(&mut Self)>(&mut self,
858 attrs
: &[ast
::Attribute
],
860 let mut attrs
= Attributes
::from_ast(self.sess
.diagnostic(), attrs
);
861 if let Some(ref cfg
) = attrs
.cfg
{
862 if !cfg
.matches(&self.sess
.parse_sess
, Some(&self.sess
.features_untracked())) {
867 let has_name
= !name
.is_empty();
869 self.collector
.names
.push(name
);
872 attrs
.collapse_doc_comments();
873 attrs
.unindent_doc_comments();
874 // The collapse-docs pass won't combine sugared/raw doc attributes, or included files with
875 // anything else, this will combine them for us.
876 if let Some(doc
) = attrs
.collapsed_doc_value() {
877 self.collector
.set_position(attrs
.span
.unwrap_or(DUMMY_SP
));
878 markdown
::find_testable_code(&doc
,
881 self.collector
.enable_per_target_ignores
);
887 self.collector
.names
.pop();
892 impl<'a
, 'hir
> intravisit
::Visitor
<'hir
> for HirCollector
<'a
, 'hir
> {
893 fn nested_visit_map
<'this
>(&'this
mut self) -> intravisit
::NestedVisitorMap
<'this
, 'hir
> {
894 intravisit
::NestedVisitorMap
::All(&self.map
)
897 fn visit_item(&mut self, item
: &'hir hir
::Item
) {
898 let name
= if let hir
::ItemKind
::Impl(.., ref ty
, _
) = item
.kind
{
899 self.map
.hir_to_pretty_string(ty
.hir_id
)
901 item
.ident
.to_string()
904 self.visit_testable(name
, &item
.attrs
, |this
| {
905 intravisit
::walk_item(this
, item
);
909 fn visit_trait_item(&mut self, item
: &'hir hir
::TraitItem
) {
910 self.visit_testable(item
.ident
.to_string(), &item
.attrs
, |this
| {
911 intravisit
::walk_trait_item(this
, item
);
915 fn visit_impl_item(&mut self, item
: &'hir hir
::ImplItem
) {
916 self.visit_testable(item
.ident
.to_string(), &item
.attrs
, |this
| {
917 intravisit
::walk_impl_item(this
, item
);
921 fn visit_foreign_item(&mut self, item
: &'hir hir
::ForeignItem
) {
922 self.visit_testable(item
.ident
.to_string(), &item
.attrs
, |this
| {
923 intravisit
::walk_foreign_item(this
, item
);
927 fn visit_variant(&mut self,
928 v
: &'hir hir
::Variant
,
929 g
: &'hir hir
::Generics
,
930 item_id
: hir
::HirId
) {
931 self.visit_testable(v
.ident
.to_string(), &v
.attrs
, |this
| {
932 intravisit
::walk_variant(this
, v
, g
, item_id
);
936 fn visit_struct_field(&mut self, f
: &'hir hir
::StructField
) {
937 self.visit_testable(f
.ident
.to_string(), &f
.attrs
, |this
| {
938 intravisit
::walk_struct_field(this
, f
);
942 fn visit_macro_def(&mut self, macro_def
: &'hir hir
::MacroDef
) {
943 self.visit_testable(macro_def
.name
.to_string(), ¯o_def
.attrs
, |_
| ());