- Notifications
You must be signed in to change notification settings - Fork 14k
rustdoc: Properly detect syntactically invalid doctests (to fix a regression) #148101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -205,11 +205,6 @@ pub trait Emitter { | |
| true | ||
| } | ||
| | ||
| /// Checks if we can use colors in the current output stream. | ||
| fn supports_color(&self) -> bool { | ||
| false | ||
| } | ||
| Comment on lines -208 to -211 Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See #148101 (comment). | ||
| | ||
| fn source_map(&self) -> Option<&SourceMap>; | ||
| | ||
| fn translator(&self) -> &Translator; | ||
| | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -2,19 +2,18 @@ | |
| //! runnable, e.g. by adding a `main` function if it doesn't already exist. | ||
| | ||
| use std::fmt::{self, Write as _}; | ||
| use std::io; | ||
| 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::emitter::stderr_destination; | ||
| use rustc_errors::{AutoStream, ColorConfig, DiagCtxtHandle}; | ||
| use rustc_errors::emitter::SilentEmitter; | ||
| use rustc_errors::{DiagCtxt, DiagCtxtHandle}; | ||
| use rustc_parse::lexer::StripTokens; | ||
| use rustc_parse::new_parser_from_source_str; | ||
| use rustc_session::parse::ParseSess; | ||
| use rustc_span::edition::{DEFAULT_EDITION, Edition}; | ||
| use rustc_span::source_map::SourceMap; | ||
| use rustc_span::source_map::{FilePathMapping, SourceMap}; | ||
| use rustc_span::symbol::sym; | ||
| use rustc_span::{DUMMY_SP, FileName, Span, kw}; | ||
| use tracing::debug; | ||
| | @@ -445,40 +444,33 @@ fn parse_source( | |
| parent_dcx: Option<DiagCtxtHandle<'_>>, | ||
| span: Span, | ||
| ) -> Result<ParseSourceInfo, ()> { | ||
| use rustc_errors::DiagCtxt; | ||
| use rustc_errors::emitter::{Emitter, HumanEmitter}; | ||
| use rustc_span::source_map::FilePathMapping; | ||
| | ||
| let mut info = | ||
| ParseSourceInfo { already_has_extern_crate: crate_name.is_none(), ..Default::default() }; | ||
| | ||
| let wrapped_source = format!("{DOCTEST_CODE_WRAPPER}{source}\n}}"); | ||
| | ||
| let filename = FileName::anon_source_code(&wrapped_source); | ||
| | ||
| let sm = Arc::new(SourceMap::new(FilePathMapping::empty())); | ||
| let translator = rustc_driver::default_translator(); | ||
| info.supports_color = | ||
| HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator.clone()) | ||
| .supports_color(); | ||
| Comment on lines -461 to -463 Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've now fully removed this logic. Before I rebased past #147207, I actually simplified it to However, since said PR (#147207: migrating coloring crates), You could argue that I should keep it and patch
| ||
| // Any errors in parsing should also appear when the doctest is compiled for real, so just | ||
| // send all the errors that the parser emits directly into a `Sink` instead of stderr. | ||
| let emitter = HumanEmitter::new(AutoStream::never(Box::new(io::sink())), translator); | ||
| Comment on lines -464 to -466 Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It makes no sense to use a | ||
| // Any parse errors should also appear when the doctest is compiled for real, | ||
| // so just suppress them here. | ||
| Comment on lines +425 to +426 Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also for e.g., Therefore elaborate that source comment. | ||
| let emitter = SilentEmitter { translator: rustc_driver::default_translator() }; | ||
| | ||
| // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser | ||
| let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); | ||
| let psess = ParseSess::with_dcx(dcx, sm); | ||
| let psess = ParseSess::with_dcx(dcx, Arc::new(SourceMap::new(FilePathMapping::empty()))); | ||
| | ||
| // Don't strip any tokens; it wouldn't matter anyway because the source is wrapped in a function. | ||
| let mut parser = | ||
| match new_parser_from_source_str(&psess, filename, wrapped_source, StripTokens::Nothing) { | ||
| Ok(p) => p, | ||
| Err(errs) => { | ||
| errs.into_iter().for_each(|err| err.cancel()); | ||
| reset_error_count(&psess); | ||
| return Err(()); | ||
| } | ||
| }; | ||
| let mut parser = match new_parser_from_source_str( | ||
| &psess, | ||
| FileName::anon_source_code(&wrapped_source), | ||
| wrapped_source, | ||
| StripTokens::Nothing, | ||
| ) { | ||
| Ok(p) => p, | ||
| Err(errs) => { | ||
| errs.into_iter().for_each(|err| err.cancel()); | ||
| reset_error_count(&psess); | ||
| return Err(()); | ||
| } | ||
| }; | ||
| | ||
| fn push_to_s(s: &mut String, source: &str, span: rustc_span::Span, prev_span_hi: &mut usize) { | ||
| let extra_len = DOCTEST_CODE_WRAPPER.len(); | ||
| | @@ -531,7 +523,12 @@ fn parse_source( | |
| let result = match parsed { | ||
| Ok(Some(ref item)) | ||
| if let ast::ItemKind::Fn(ref fn_item) = item.kind | ||
| && let Some(ref body) = fn_item.body => | ||
| && let Some(ref body) = fn_item.body | ||
| // The parser might've recovered from some syntax errors and thus returned `Ok(_)`. | ||
| // However, since we've suppressed diagnostic emission above, we must ensure that | ||
| // we mark the doctest as syntactically invalid. Otherwise, we can end up generating | ||
| // a runnable doctest that's free of any errors even though the source was malformed. | ||
| && psess.dcx().has_errors().is_none() => | ||
| { | ||
| for attr in &item.attrs { | ||
| if attr.style == AttrStyle::Outer || attr.has_any_name(not_crate_attrs) { | ||
| | @@ -587,14 +584,9 @@ fn parse_source( | |
| } | ||
| } | ||
| } | ||
| StmtKind::Expr(ref expr) => { | ||
| if matches!(expr.kind, ast::ExprKind::Err(_)) { | ||
| reset_error_count(&psess); | ||
| return Err(()); | ||
| } | ||
| Comment on lines -591 to -594 Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We no longer need to do that. Due to the | ||
| has_non_items = true; | ||
| StmtKind::Let(_) | StmtKind::Expr(_) | StmtKind::Semi(_) | StmtKind::Empty => { | ||
| has_non_items = true | ||
| } | ||
| StmtKind::Let(_) | StmtKind::Semi(_) | StmtKind::Empty => has_non_items = true, | ||
| } | ||
| | ||
| // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to | ||
| | ||
| Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here the parser recovers from a missing semicolon and returns Now that we consider it syntactically invalid (rightfully so!), it no longer enjoys this privilege. Hence the warning. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| | @@ -14,5 +14,12 @@ LL | Input: 123 | |
| LL ~ } } | ||
| | | ||
| | ||
| error: aborting due to 1 previous error | ||
| error[E0601]: `main` function not found in crate `rust_out` | ||
| Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I consider this an unfortunate regression. As mentioned in my other review comment, this doctest no longer gets preprocessed (like adding a If you like to see this addressed, I can of course change it to not bail out on However in general (not necessarily in this specific case), wrapping invalid doctests in a function is always a trade-off (it may lead to better errors in some cases but to worse ones in others; and it may leak internal names in diagnostics like Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, due to the existence of error code checks like | ||
| --> $DIR/nocapture-fail.rs:10:2 | ||
| | | ||
| LL | } | ||
| | ^ consider adding a `main` function to `$DIR/nocapture-fail.rs` | ||
| | ||
| error: aborting due to 2 previous errors | ||
| | ||
| For more information about this error, try `rustc --explain E0601`. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| // issue: <https://github.com/rust-lang/rust/issues/147999> | ||
| Member Author There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [add description] | ||
| //@ compile-flags: --test | ||
| //@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR" | ||
| //@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME" | ||
| //@ failure-status: 101 | ||
| | ||
| //! ``` | ||
| //! #[derive(Clone)] | ||
| //! ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| | ||
| running 1 test | ||
| test $DIR/recovered-syntax-error.rs - (line 7) ... FAILED | ||
| | ||
| failures: | ||
| | ||
| ---- $DIR/recovered-syntax-error.rs - (line 7) stdout ---- | ||
| error: expected item after attributes | ||
| --> $DIR/recovered-syntax-error.rs:8:1 | ||
| | | ||
| LL | #[derive(Clone)] | ||
| | ^^^^^^^^^^^^^^^^ | ||
| | ||
| error: aborting due to 1 previous error | ||
| | ||
| Couldn't compile the test. | ||
| | ||
| failures: | ||
| $DIR/recovered-syntax-error.rs - (line 7) | ||
| | ||
| test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME | ||
| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prime example ^^