-
Notifications
You must be signed in to change notification settings - Fork 13.9k
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: master
Are you sure you want to change the base?
Conversation
This comment has been minimized.
This comment has been minimized.
| // 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. |
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.
[rework comment]
| // 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); |
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.
It makes no sense to use a HumanEmitter here that sends things into the void. I'm pretty sure that leads to extra work (rendering the diagnostic) that gets thrown away immediately after. Use a SilentEmitter instead which exists precisely for such use cases.
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.
Here the parser recovers from a missing semicolon and returns Ok(_). As a result, we used to consider the doctest syntactically valid and thus applied all the usual preprocessing, most importantly adding #![allow(unused)].
Now that we consider it syntactically invalid (rightfully so!), it no longer enjoys this privilege. Hence the warning.
| | | ||
|
|
||
| error: aborting due to 1 previous error | ||
| error[E0601]: `main` function not found in crate `rust_out` |
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.
I consider this an unfortunate regression. As mentioned in my other review comment, this doctest no longer gets preprocessed (like adding a main if absent).
If you like to see this addressed, I can of course change it to not bail out on has_errors (but still set failed_to_parse to true) in order to forward has_main and so on correctly (the code might get a bit gnarly tho).
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 _doctest_main_tests_rustdoc_ui_doctest_comment_in_attr_134221_2_rs_11_0; see #142446 (comment) for context).
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.
Okay, due to the existence of error code checks like compile_fail,ENNNN I have to preserve some of the original behavior actually (namely continuing to preprocess certain syntactically invalid doctests (at the very least ones that have syntax errors that the parser recovered from)).
| @@ -0,0 +1,9 @@ | |||
| // issue: <https://github.com/rust-lang/rust/issues/147999> | |||
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.
[add description]
| if self.failed_to_parse { | ||
| // If we failed to parse the doctest, there's no need to go generate a complete doctest, | ||
| // the diagnostics will be better this way. | ||
| debug!("failed to parse doctest:\n{test_code}"); |
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.
Applies #138104 (comment).
| let dcx = DiagCtxt::new(Box::new(emitter)).disable_warnings(); | ||
| let psess = ParseSess::with_dcx(dcx, Arc::new(SourceMap::new(FilePathMapping::empty()))); | ||
| // Reset errors at the end so that we don't panic on delayed bugs. | ||
| let _defer = rustc_data_structures::defer(|| psess.dcx().reset_err_count()); |
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.
Instead of having to ensure that we call reset_err_count() at every possible function exit, just use defer to make it happen automatically which is a lot more robust and legible.
| info.supports_color = | ||
| HumanEmitter::new(stderr_destination(ColorConfig::Auto), translator.clone()) | ||
| .supports_color(); |
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.
I've now fully removed this logic. Before I rebased past #147207, I actually simplified it to info.supports_color = stderr_destination(ColorConfig::Auto).supports_color();.
However, since said PR (#147207: migrating coloring crates), HumanEmitter::supports_color() unconditionally(!) returns false (in fact, Emitter::supports_color is no longer used by anyone else and should be removed), so there's no reason to keep it. Rephrased, since that PR all compiler diagnostics for doctests are uncolored.
You could argue that I should keep it and patch supports_color in rustc to "work again". However, I'd rather rework our doctest coloring wholesale in a separate PR. At least before that migration PR, our setup was quite busted:
- First of all, it didn't query+set
supports_colorfor syntactically invalid doctests, so syntax errors were always shown without color (contrary to e.g., name resolution errors). - Second of all, calling
supports_color()here was quite frankly wrong: Piping the output ofrustdoc … --testinto a file (or| cator whatever) did not suppress colors. I'm not actually sure if we can ever address that nicely (without stripping ANSI codes after the fact) since we pass that diagnostic tolibtest, right? I might very well be wrong here, maybe it's a non-issue.
| if matches!(expr.kind, ast::ExprKind::Err(_)) { | ||
| reset_error_count(&psess); | ||
| return Err(()); | ||
| } |
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.
We no longer need to do that. Due to the ErrorGuaranteed in ExprKind::Err we know that an error diagnostic had to be emitted prior meaning dcx.has_errors() has to be some (meaning we already bailed out with an Err(_) above)!
That is, even in the presence of parse error recovery.
81c015e to
066a329
Compare
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 ^^
| /// Checks if we can use colors in the current output stream. | ||
| fn supports_color(&self) -> bool { | ||
| false | ||
| } |
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.
See #148101 (comment).
|
The job Click to see the possible cause of the failure (guessed by this bot) |
| // Any parse errors should also appear when the doctest is compiled for real, | ||
| // so just suppress them here. |
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.
Also for e.g., compile_fail we don't necessary want to output any errors "unless e.g., --nocapture or --test-args --show-output" …; so it really isn't our responsibility.
Therefore elaborate that source comment.
Previously, we would only consider doctests to be syntactically invalid (for our crude static analysis pass) if (we failed to lex it or if) the parser returned an
Err(_)1.However, since the parser can actually recover from a slew of invalid forms, it may return
Ok(node)even in the presence of syntax errors. E.g., stray outer attrs may just get jettisoned. Combine that with the fact that we (send all diagnostics into the ether and) only forward selected spans of the original source to the final runnable doctest, it led to issues like #147999.To remedy that, check if
dcx.has_errors().Fixes #147999 (regressed in #138104).
FIXME: Okay, due to the existence of error code checks like
compile_fail,E0726I actually have to preserve some existing behavior here and continue wrapping certain classes of syntactically invalid doctests in functions, sigh.Footnotes
Or if the one of the expr stmts was an
ExprKind::Err(_)(a special case / hack to paper over things). ↩