Rollup merge of #148834 - Muscraft:fix-doctest-colors, r=fmease

fix(rustdoc): Color doctest errors

`@fmease's` [Deep analysis](https://github.com/rust-lang/rust/issues/148749#issuecomment-3508455278) on the problem
> Yeah, here's a deep analysis by me from a few weeks back of what's going on ([#148101 (comment)](https://github.com/rust-lang/rust/pull/148101#discussion_r2462756875)):
>
> > […]
> > However, since said PR ([#147207](https://github.com/rust-lang/rust/pull/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:
> >
> > 1. First of all, it didn't query+set `supports_color` for syntactically invalid doctests, so syntax errors were always shown without color (contrary to e.g., name resolution errors).
> > 2. Second of all, calling `supports_color()` here was quite frankly wrong: Piping the output of `rustdoc … --test` into a file (or `| cat` or 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 to `libtest`, right? I might very well be wrong here, maybe it's a non-issue.

<hr>

```rust
/// ```
/// foo
/// ```
fn foo() {}
```
```
rustdoc --test lib.rs
```

Stable:
<img width="377" height="290" alt="stable" src="https://github.com/user-attachments/assets/cd20f947-b58d-42db-8735-797613baa9cc" />

Beta:
<img width="377" height="290" alt="beta" src="https://github.com/user-attachments/assets/f02588fd-41d2-4642-b03a-5554a68671eb" />

Nightly:
<img width="377" height="290" alt="nightly" src="https://github.com/user-attachments/assets/871cb417-f47e-4058-8a76-3bcd538ce141" />

After:
<img width="377" height="290" alt="after" src="https://github.com/user-attachments/assets/5734c01f-3f1c-44bb-9404-628c0c33b440" />

Note: This will need to be backported to `beta`

Fixes: rust-lang/rust#148749
This commit is contained in:
Stuart Cook 2025-11-12 12:26:43 +11:00 committed by GitHub
commit 0675923ef8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 15 additions and 12 deletions

View file

@ -3466,14 +3466,9 @@ impl Drop for Buffy {
pub fn stderr_destination(color: ColorConfig) -> Destination {
let buffer_writer = std::io::stderr();
let choice = color.to_color_choice();
// We need to resolve `ColorChoice::Auto` before `Box`ing since
// `ColorChoice::Auto` on `dyn Write` will always resolve to `Never`
let choice = if matches!(choice, ColorChoice::Auto) {
AutoStream::choice(&buffer_writer)
} else {
choice
};
let choice = get_stderr_color_choice(color, &buffer_writer);
// On Windows we'll be performing global synchronization on the entire
// system for emitting rustc errors, so there's no need to buffer
// anything.
@ -3488,6 +3483,11 @@ pub fn stderr_destination(color: ColorConfig) -> Destination {
}
}
pub fn get_stderr_color_choice(color: ColorConfig, stderr: &std::io::Stderr) -> ColorChoice {
let choice = color.to_color_choice();
if matches!(choice, ColorChoice::Auto) { AutoStream::choice(stderr) } else { choice }
}
/// On Windows, BRIGHT_BLUE is hard to read on black. Use cyan instead.
///
/// See #36178.

View file

@ -8,8 +8,8 @@ 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::get_stderr_color_choice;
use rustc_errors::{AutoStream, ColorChoice, ColorConfig, DiagCtxtHandle};
use rustc_parse::lexer::StripTokens;
use rustc_parse::new_parser_from_source_str;
use rustc_session::parse::ParseSess;
@ -446,7 +446,7 @@ fn parse_source(
span: Span,
) -> Result<ParseSourceInfo, ()> {
use rustc_errors::DiagCtxt;
use rustc_errors::emitter::{Emitter, HumanEmitter};
use rustc_errors::emitter::HumanEmitter;
use rustc_span::source_map::FilePathMapping;
let mut info =
@ -458,9 +458,12 @@ fn parse_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();
let supports_color = match get_stderr_color_choice(ColorConfig::Auto, &std::io::stderr()) {
ColorChoice::Auto => unreachable!(),
ColorChoice::AlwaysAnsi | ColorChoice::Always => true,
ColorChoice::Never => false,
};
info.supports_color = supports_color;
// 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);