|
2 | 2 | //! runnable, e.g. by adding a `main` function if it doesn't already exist. |
3 | 3 |
|
4 | 4 | use std::fmt::{self, Write as _}; |
5 | | -use std::io; |
6 | 5 | use std::sync::Arc; |
7 | 6 |
|
8 | 7 | use rustc_ast::token::{Delimiter, TokenKind}; |
9 | 8 | use rustc_ast::tokenstream::TokenTree; |
10 | 9 | use rustc_ast::{self as ast, AttrStyle, HasAttrs, StmtKind}; |
11 | | -use rustc_errors::emitter::stderr_destination; |
12 | | -use rustc_errors::{AutoStream, ColorConfig, DiagCtxtHandle}; |
| 10 | +use rustc_errors::emitter::SilentEmitter; |
| 11 | +use rustc_errors::{DiagCtxt, DiagCtxtHandle}; |
13 | 12 | use rustc_parse::lexer::StripTokens; |
14 | 13 | use rustc_parse::new_parser_from_source_str; |
15 | 14 | use rustc_session::parse::ParseSess; |
16 | 15 | use rustc_span::edition::{DEFAULT_EDITION, Edition}; |
17 | | -use rustc_span::source_map::SourceMap; |
| 16 | +use rustc_span::source_map::{FilePathMapping, SourceMap}; |
18 | 17 | use rustc_span::symbol::sym; |
19 | 18 | use rustc_span::{DUMMY_SP, FileName, Span, kw}; |
20 | 19 | use tracing::debug; |
@@ -445,40 +444,33 @@ fn parse_source( |
445 | 444 | parent_dcx: Option<DiagCtxtHandle<'_>>, |
446 | 445 | span: Span, |
447 | 446 | ) -> Result<ParseSourceInfo, ()> { |
448 | | - use rustc_errors::DiagCtxt; |
449 | | - use rustc_errors::emitter::{Emitter, HumanEmitter}; |
450 | | - use rustc_span::source_map::FilePathMapping; |
451 | | - |
452 | 447 | let mut info = |
453 | 448 | ParseSourceInfo { already_has_extern_crate: crate_name.is_none(), ..Default::default() }; |
454 | 449 |
|
455 | 450 | let wrapped_source = format!("{DOCTEST_CODE_WRAPPER}{source}\n}}"); |
456 | 451 |
|
457 | | - let filename = FileName::anon_source_code(&wrapped_source); |
458 | | - |
459 | | - let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); |
460 | | - let translator = rustc_driver::default_translator(); |
461 | | - info.supports_color = |
462 | | - HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator.clone()) |
463 | | - .supports_color(); |
464 | | - // Any errors in parsing should also appear when the doctest is compiled for real, so just |
465 | | - // send all the errors that the parser emits directly into a `Sink` instead of stderr. |
466 | | - let emitter = HumanEmitter::new(AutoStream::never(Box::new(io::sink())), translator); |
| 452 | + // Any parse errors should also appear when the doctest is compiled for real, |
| 453 | + // so just suppress them here. |
| 454 | + let emitter = SilentEmitter { translator: rustc_driver::default_translator() }; |
467 | 455 |
|
468 | 456 | // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser |
469 | 457 | let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); |
470 | | - let psess = ParseSess::with_dcx(dcx, sm); |
| 458 | + let psess = ParseSess::with_dcx(dcx, Arc::new(SourceMap::new(FilePathMapping::empty()))); |
471 | 459 |
|
472 | 460 | // Don't strip any tokens; it wouldn't matter anyway because the source is wrapped in a function. |
473 | | - let mut parser = |
474 | | - match new_parser_from_source_str(&psess, filename, wrapped_source, StripTokens::Nothing) { |
475 | | - Ok(p) => p, |
476 | | - Err(errs) => { |
477 | | - errs.into_iter().for_each(|err| err.cancel()); |
478 | | - reset_error_count(&psess); |
479 | | - return Err(()); |
480 | | - } |
481 | | - }; |
| 461 | + let mut parser = match new_parser_from_source_str( |
| 462 | + &psess, |
| 463 | + FileName::anon_source_code(&wrapped_source), |
| 464 | + wrapped_source, |
| 465 | + StripTokens::Nothing, |
| 466 | + ) { |
| 467 | + Ok(p) => p, |
| 468 | + Err(errs) => { |
| 469 | + errs.into_iter().for_each(|err| err.cancel()); |
| 470 | + reset_error_count(&psess); |
| 471 | + return Err(()); |
| 472 | + } |
| 473 | + }; |
482 | 474 |
|
483 | 475 | fn push_to_s(s: &mut String, source: &str, span: rustc_span::Span, prev_span_hi: &mut usize) { |
484 | 476 | let extra_len = DOCTEST_CODE_WRAPPER.len(); |
@@ -531,7 +523,12 @@ fn parse_source( |
531 | 523 | let result = match parsed { |
532 | 524 | Ok(Some(ref item)) |
533 | 525 | if let ast::ItemKind::Fn(ref fn_item) = item.kind |
534 | | - && let Some(ref body) = fn_item.body => |
| 526 | + && let Some(ref body) = fn_item.body |
| 527 | + // The parser might've recovered from some syntax errors and thus returned `Ok(_)`. |
| 528 | + // However, since we've suppressed diagnostic emission above, we must ensure that |
| 529 | + // we mark the doctest as syntactically invalid. Otherwise, we can end up generating |
| 530 | + // a runnable doctest that's free of any errors even though the source was malformed. |
| 531 | + && psess.dcx().has_errors().is_none() => |
535 | 532 | { |
536 | 533 | for attr in &item.attrs { |
537 | 534 | if attr.style == AttrStyle::Outer || attr.has_any_name(not_crate_attrs) { |
@@ -587,14 +584,9 @@ fn parse_source( |
587 | 584 | } |
588 | 585 | } |
589 | 586 | } |
590 | | - StmtKind::Expr(ref expr) => { |
591 | | - if matches!(expr.kind, ast::ExprKind::Err(_)) { |
592 | | - reset_error_count(&psess); |
593 | | - return Err(()); |
594 | | - } |
595 | | - has_non_items = true; |
| 587 | + StmtKind::Let(_) | StmtKind::Expr(_) | StmtKind::Semi(_) | StmtKind::Empty => { |
| 588 | + has_non_items = true |
596 | 589 | } |
597 | | - StmtKind::Let(_) | StmtKind::Semi(_) | StmtKind::Empty => has_non_items = true, |
598 | 590 | } |
599 | 591 |
|
600 | 592 | // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to |
|
0 commit comments