Skip to content

Conversation

@JayanAXHF
Copy link
Member

@JayanAXHF JayanAXHF commented Jan 9, 2026

This PR adds a feature that enables rustc --explain <error> to have syntax highlighted code blocks. Due to performance, size and complexity constraints, the highlighter is very heuristc, relying on conventions for capitalizations and such to infer what an identifier represents. The details for the implementation are specified below.

Changes

  1. Change term::entrypoint to term::entrypoint_with_formatter, which takes an optional third argument, which is a function pointer to a formatter. (compiler/rustc_errors/src/markdown/mod.rs)
  2. Change MdStream::write_anstream_buf to be a wrapper around a new function, MdStream::write_anstream_buf_with_formatter, which takes a function pointer to a formatter. (compiler/rustc_errors/src/markdown/mod.rs)
  3. Change compiler/rustc_driver_impl/src/lib.rs to call MdStream::write_anstream_buf_with_formatter instead of MdStream::write_anstream_buf.
  4. Add a compiler/rustc_driver_impl/src/highlighter.rs file, which contains the actual syntax highlighter.

Implementation Details

  1. The highlighter starts from the highlight function defined in compiler/rustc_driver_impl/src/highlighter.rs. It creates a new instance of the Highlighter struct, and calls its highlight_rustc_lexer function to start highlighting.
  2. The highlight_rustc_lexer function uses rustc_lexer to lex the code into Tokens. rustc_lexer was chosen since it preserves the newlines after scanning.
  3. Based on the kind of token (TokenKind), we color the corresponding lexeme.

Highlighter Implementation

Identifiers

  1. All identifiers that match a (non-exhaustive and minimal) list of keywords are coloured magenta.
  2. An identifier that begins with a capital letter is assumed as a type. There is no distinction between a Trait and a type, since that would involve name resolution, and the parts of rustc that perform name resolution on code do not preserve the original formatting. (An attempt to use rustc_parse's lexer and TokenStream was made, which was then printed with the pretty printer, but failed to preserve the formatting and was generally more complex to work with)
  3. An identifier that is immediately followed by a parenthesis is recognized as a function identifier, and coloured blue.

Literals

  1. A String literal (or its corresponding Raw, C and Byte versions) is colored green.
  2. All other literals are colored bright red (orange-esque)

Everything Else

Everything else is colored bright white and dimmed, to create a grayish colour.


Demo

image Command: rustc --explain E0520

This description was not generated by an LLM (:p)

cc: @bjorn3

@rustbot
Copy link
Collaborator

rustbot commented Jan 9, 2026

These commits modify the Cargo.lock file. Unintentional changes to Cargo.lock can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jan 9, 2026
@rustbot
Copy link
Collaborator

rustbot commented Jan 9, 2026

r? @fee1-dead

rustbot has assigned @fee1-dead.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@JayanAXHF
Copy link
Member Author

PS (Link to Zulip Topic) : #t-compiler/diagnostics > Colored output for `rustc --explain`

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

Is this change requires MCP?

@JayanAXHF
Copy link
Member Author

Is this change requires MCP?

Could you clarify what you mean by MCP? I'm very new to rustc (this is my first contribution)

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

A process where you propose a change and other members brainstorm it whether it worth adding or not

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

https://forge.rust-lang.org/compiler/proposals-and-stabilization.html

@JayanAXHF
Copy link
Member Author

JayanAXHF commented Jan 9, 2026

A process where you propose a change and other members brainstorm it whether it worth adding or not

Oh thanks. I don't think that this qualifies as a major change. It just adds some very simple code that basically colours tokens by matching on its kind and lexeme. IMO it falls in the light-weight proposal category, as it doesn't break anything (if it errors, the plain block is printed)

@tgross35
Copy link
Contributor

tgross35 commented Jan 9, 2026

I don't think this needs an MCP, it's a reasonably local change that could be backed out at any time.

@Kivooeo
Copy link
Member

Kivooeo commented Jan 9, 2026

r? me

@rustbot rustbot assigned Kivooeo and unassigned fee1-dead Jan 9, 2026
@tgross35
Copy link
Contributor

tgross35 commented Jan 9, 2026

Fyi this will need a test somehow, a UI test is probably fine

@JayanAXHF
Copy link
Member Author

Fyi this will need a test somehow, a UI test is probably fine

The test could probably me giving it a string of code and comparing its colored output to what was expected.

@rust-log-analyzer

This comment has been minimized.

@JayanAXHF
Copy link
Member Author

The job aarch64-gnu-llvm-20-2 failed! Check out the build log: (web) (plain enhanced) (plain)

The existing tests need to be updated

@tgross35
Copy link
Contributor

tgross35 commented Jan 9, 2026

IIRC that test can be updated with RUSTC_BLESS=1

@JayanAXHF
Copy link
Member Author

IIRC that test can be updated with RUSTC_BLESS=1

Could you elaborate please (I'm sorry I'm new to the codebase)

@tgross35
Copy link
Contributor

tgross35 commented Jan 9, 2026

Sorry, you don't need to set that env - just run --bless when you run the tests locally (./x t <relevant path>).

let bless = std::env::var_os("RUSTC_BLESS").is_some_and(|v| v != "0");
. Just means to auto update test that check output and can be annoying to update manually, so you can look at the diff (of course, doesn't mean it is correct!).

Not actually sure why that test failed, the code formatter isn't hooked there.

@JayanAXHF
Copy link
Member Author

I have fixed the failing test.

@JayanAXHF
Copy link
Member Author

Fyi this will need a test somehow, a UI test is probably fine

Where would I create the test?

@Kivooeo
Copy link
Member

Kivooeo commented Jan 10, 2026

Can we consider colouring all primitives types as well? The same way it was made for keywords

https://doc.rust-lang.org/rust-by-example/primitives.html

@Kivooeo
Copy link
Member

Kivooeo commented Jan 10, 2026

Can write_anstream_buf now be deleted? I don't see any reasons to keep it for now, since it just calls write_anstream_buf_with_formatter, why not call it directly

Same for write_stream

@JayanAXHF
Copy link
Member Author

Can we consider colouring all primitives types as well? The same way it was made for keywords

https://doc.rust-lang.org/rust-by-example/primitives.html

Sure!

@JayanAXHF
Copy link
Member Author

Same for write_stream

This would require having to change stuff in a few places (in the same file i believe)

Can write_anstream_buf now be deleted? I don't see any reasons to keep it for now, since it just calls write_anstream_buf_with_formatter, why not call it directly

This can done, since iirc write_anstream_buf is not called anywhere except the current test (which can be updated)

@Kivooeo
Copy link
Member

Kivooeo commented Jan 10, 2026

This would require having to change stuff in a few places

Feel free to do this changes

@JayanAXHF
Copy link
Member Author

JayanAXHF commented Jan 11, 2026

but adding --color=always

What will this look like in the .stdout file?

I believe it should just show the escape codes, similar to the output.stdout file that's in this PR now (though code highlighting isn't hooked there of course)

Would it be fine if the test just focused on the highlighter?

@JayanAXHF
Copy link
Member Author

@Kivooeo (cc: @tgross35) I have figured out (i think) why --color always doesn't always print out in color. Apparently, within rustc_driver_impl,

        if io::stdout().is_terminal() {
            show_md_content_with_pager(&text, color);
        } else {
            safe_print!("{text}");
        }

prevents printing the output with color if stdout isn't a terminal. This is why --bless didn't output a .stdout file with ANSI escapes

@JayanAXHF
Copy link
Member Author

JayanAXHF commented Jan 13, 2026

Following my previous message, I have added the tests for the highlighter. However, I did have to change rustc_driver_impl's handle_explain function to be indifferent to stdout when --color always is passed. I hope that is alright

@rust-log-analyzer

This comment has been minimized.

@JayanAXHF JayanAXHF force-pushed the rustc_colored_explain branch from dddc3ea to f53159e Compare January 13, 2026 16:20
@JayanAXHF JayanAXHF force-pushed the rustc_colored_explain branch from 1755d4d to 19b04f2 Compare January 18, 2026 14:54
@Kivooeo
Copy link
Member

Kivooeo commented Jan 18, 2026

ok, looks good now

thanks for this

@bors r+

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 18, 2026

📌 Commit 19b04f2 has been approved by Kivooeo

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jan 18, 2026
@Kivooeo
Copy link
Member

Kivooeo commented Jan 18, 2026

@bors r-

wait, actually not

can you please squash commits into one before merge?

@rust-bors rust-bors bot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Jan 18, 2026
@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 18, 2026

Commit 19b04f2 has been unapproved.

@JayanAXHF JayanAXHF force-pushed the rustc_colored_explain branch from 19b04f2 to 243874a Compare January 19, 2026 10:57
@JayanAXHF
Copy link
Member Author

JayanAXHF commented Jan 19, 2026

Done!

cc: @Kivooeo

@rust-log-analyzer

This comment has been minimized.

This commit adds a heuristics-based syntax highlighter for the `rustc
--explain` command. It uses `rsutc_lexer`'s lexer to parse input in
tokens, and matches on them to determine their color.
@JayanAXHF JayanAXHF force-pushed the rustc_colored_explain branch from 243874a to 67c45b7 Compare January 19, 2026 12:16
@Kivooeo
Copy link
Member

Kivooeo commented Jan 19, 2026

@bors r+

@rust-bors
Copy link
Contributor

rust-bors bot commented Jan 19, 2026

📌 Commit 67c45b7 has been approved by Kivooeo

It is now in the queue for this repository.

@rust-bors rust-bors bot added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jan 19, 2026
rust-bors bot pushed a commit that referenced this pull request Jan 19, 2026
…uwer

Rollup of 15 pull requests

Successful merges:

 - #148623 (Ignore `#[doc(hidden)]` items when computing trimmed paths for printing)
 - #150550 (Miscellaneous cleanups to borrowck related code)
 - #150879 (Remove the diagnostic lints)
 - #150895 (rustc_errors: Add (heuristic) Syntax Highlighting for `rustc --explain`)
 - #150987 (remote-test-server: Fix header in batch mode)
 - #151004 (std: implement `sleep_until` on Apple platforms)
 - #151045 (Simplify some literal-value negations with `u128::wrapping_neg`)
 - #151119 (Support pointers in type reflection)
 - #151171 (Do not recover from `Trait()` if generic list is unterminated)
 - #151231 (HIR typeck cleanup: clarify and re-style `check_expr_unop`)
 - #151249 (Parse ident with allowing recovery when trying to diagnose)
 - #151295 (THIR patterns: Use `ty::Value` in more places throughout `const_to_pat`)
 - #151326 (Remove `DiagMessage::Translated` in favour of `DiagMessage::Str`)
 - #151361 (add test for issue 61463)
 - #151371 (Add `S-blocked` to `labels_blocking_approval`)

r? @ghost
@rust-bors rust-bors bot merged commit 8a22bab into rust-lang:main Jan 20, 2026
11 checks passed
rust-timer added a commit that referenced this pull request Jan 20, 2026
Rollup merge of #150895 - rustc_colored_explain, r=Kivooeo

rustc_errors: Add (heuristic) Syntax Highlighting for `rustc --explain`

This PR adds a feature that enables `rustc --explain <error>` to have syntax highlighted code blocks. Due to performance, size and complexity constraints, the highlighter is very heuristc, relying on conventions for capitalizations and such to infer what an identifier represents. The details for the implementation are specified below.
# Changes
1. Change `term::entrypoint` to `term::entrypoint_with_formatter`, which takes an optional third argument, which is a function pointer to a formatter. ([compiler/rustc_errors/src/markdown/mod.rs](https://github.com/rust-lang/rust/compare/main...JayanAXHF:rust:rustc_colored_explain?expand=1#diff-a6e139cadbc2e6922d816eb08f9e2c7b48304d09e6588227e2b70215c4f0725c))
2. Change `MdStream::write_anstream_buf` to be a wrapper around a new function, `MdStream::write_anstream_buf_with_formatter`, which takes a function pointer to a formatter. ([compiler/rustc_errors/src/markdown/mod.rs](https://github.com/rust-lang/rust/compare/main...JayanAXHF:rust:rustc_colored_explain?expand=1#diff-a6e139cadbc2e6922d816eb08f9e2c7b48304d09e6588227e2b70215c4f0725c))
3. Change [`compiler/rustc_driver_impl/src/lib.rs`](https://github.com/rust-lang/rust/compare/main...JayanAXHF:rust:rustc_colored_explain?expand=1#diff-39877a2556ea309c89384956740d5892a59cef024aa9473cce16bbdd99287937) to call `MdStream::write_anstream_buf_with_formatter` instead of `MdStream::write_anstream_buf`.
4. Add a `compiler/rustc_driver_impl/src/highlighter.rs` file, which contains the actual syntax highlighter.

# Implementation Details
1. The highlighter starts from the `highlight` function defined in `compiler/rustc_driver_impl/src/highlighter.rs`. It creates a new instance of the `Highlighter` struct, and calls its `highlight_rustc_lexer` function to start highlighting.
2. The `highlight_rustc_lexer` function uses `rustc_lexer` to lex the code into `Token`s. `rustc_lexer` was chosen since it preserves the newlines after scanning.
3. Based on the kind of token (`TokenKind`), we color the corresponding lexeme.
## Highlighter Implementation
### Identifiers
1. All identifiers that match a (non-exhaustive and minimal) list of keywords are coloured magenta.
2. An identifier that begins with a capital letter is assumed as a type. There is no distinction between a `Trait` and a type, since that would involve name resolution, and the parts of `rustc` that perform name resolution on code do not preserve the original formatting. (An attempt to use `rustc_parse`'s lexer and `TokenStream` was made, which was then printed with the pretty printer, but failed to preserve the formatting and was generally more complex to work with)
3. An identifier that is immediately followed by a parenthesis is recognized as a function identifier, and coloured blue.
## Literals
5. A `String` literal (or its corresponding `Raw`, `C` and `Byte` versions) is colored green.
6. All other literals are colored bright red (orange-esque)
## Everything Else

Everything else is colored bright white and dimmed, to create a grayish colour.

---
# Demo
<img width="1864" height="2136" alt="image" src="https://github.com/user-attachments/assets/b17d3a71-e641-4457-be85-5e5b1cea2954" />

<caption> Command: <code>rustc --explain E0520</code> </caption>

---
This description was not generated by an LLM (:p)

cc: @bjorn3
github-actions bot pushed a commit to rust-lang/miri that referenced this pull request Jan 20, 2026
…uwer

Rollup of 15 pull requests

Successful merges:

 - rust-lang/rust#148623 (Ignore `#[doc(hidden)]` items when computing trimmed paths for printing)
 - rust-lang/rust#150550 (Miscellaneous cleanups to borrowck related code)
 - rust-lang/rust#150879 (Remove the diagnostic lints)
 - rust-lang/rust#150895 (rustc_errors: Add (heuristic) Syntax Highlighting for `rustc --explain`)
 - rust-lang/rust#150987 (remote-test-server: Fix header in batch mode)
 - rust-lang/rust#151004 (std: implement `sleep_until` on Apple platforms)
 - rust-lang/rust#151045 (Simplify some literal-value negations with `u128::wrapping_neg`)
 - rust-lang/rust#151119 (Support pointers in type reflection)
 - rust-lang/rust#151171 (Do not recover from `Trait()` if generic list is unterminated)
 - rust-lang/rust#151231 (HIR typeck cleanup: clarify and re-style `check_expr_unop`)
 - rust-lang/rust#151249 (Parse ident with allowing recovery when trying to diagnose)
 - rust-lang/rust#151295 (THIR patterns: Use `ty::Value` in more places throughout `const_to_pat`)
 - rust-lang/rust#151326 (Remove `DiagMessage::Translated` in favour of `DiagMessage::Str`)
 - rust-lang/rust#151361 (add test for issue 61463)
 - rust-lang/rust#151371 (Add `S-blocked` to `labels_blocking_approval`)

r? @ghost
github-actions bot pushed a commit to rust-lang/rust-analyzer that referenced this pull request Jan 22, 2026
…uwer

Rollup of 15 pull requests

Successful merges:

 - rust-lang/rust#148623 (Ignore `#[doc(hidden)]` items when computing trimmed paths for printing)
 - rust-lang/rust#150550 (Miscellaneous cleanups to borrowck related code)
 - rust-lang/rust#150879 (Remove the diagnostic lints)
 - rust-lang/rust#150895 (rustc_errors: Add (heuristic) Syntax Highlighting for `rustc --explain`)
 - rust-lang/rust#150987 (remote-test-server: Fix header in batch mode)
 - rust-lang/rust#151004 (std: implement `sleep_until` on Apple platforms)
 - rust-lang/rust#151045 (Simplify some literal-value negations with `u128::wrapping_neg`)
 - rust-lang/rust#151119 (Support pointers in type reflection)
 - rust-lang/rust#151171 (Do not recover from `Trait()` if generic list is unterminated)
 - rust-lang/rust#151231 (HIR typeck cleanup: clarify and re-style `check_expr_unop`)
 - rust-lang/rust#151249 (Parse ident with allowing recovery when trying to diagnose)
 - rust-lang/rust#151295 (THIR patterns: Use `ty::Value` in more places throughout `const_to_pat`)
 - rust-lang/rust#151326 (Remove `DiagMessage::Translated` in favour of `DiagMessage::Str`)
 - rust-lang/rust#151361 (add test for issue 61463)
 - rust-lang/rust#151371 (Add `S-blocked` to `labels_blocking_approval`)

r? @ghost
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants