The missing source map problem
You are stepping through a type inference bug in rustc_hir_analysis. You drop a breakpoint on a function that looks straightforward. You hit run. The debugger skips right past it, or worse, shows a wall of unresolved symbols and assembly-like noise. You are trying to read the compiler's own logic, but your IDE is flying blind.
This happens because RustRover and other IntelliJ-based editors do not guess where source files live. They rely entirely on Cargo.toml to map crate names to directories. When you depend on compiler internals through standard registry versions or extern crate declarations, the IDE only sees compiled metadata. It knows the crate exists. It knows the function signatures. It has no idea where the actual .rs files are stored on your disk.
You need to hand the IDE a direct map to the raw source tree. The community solves this with a single hidden command that rewrites your dependency graph to point at a local checkout of the rust-lang/rust repository.
Why the IDE goes blind
IntelliJ indexes code by walking dependency trees. It reads Cargo.toml, resolves each dependency to a path, and scans those paths for .rs files. When a dependency comes from crates.io, the IDE downloads the source archive and indexes it. When a dependency is a compiler internal like rustc_driver or rustc_middle, crates.io does not host it. You usually pull it in through a local development workflow or a custom registry.
Without explicit path mapping, the IDE treats those crates as black boxes. It can show you the public API surface. It cannot show you the implementation. Breakpoints require a direct file path and line number mapping. The debugger refuses to attach to code it cannot locate.
Think of it like trying to navigate a city using only a list of street names without a map. You know the streets exist. You cannot find them. The IDE needs coordinates.
The bridge command
The fix lives in the cargo-dev tool, which ships with the rust-lang/rust repository. It contains a subcommand specifically designed to patch your project's manifest files for IDE consumption.
Open your terminal inside your Rust project root. Run the setup command with the absolute path to your local compiler checkout:
cargo dev setup intellij --repo-path /absolute/path/to/rust-lang/rust
The command scans your workspace. It finds every Cargo.toml that references rustc crates. It replaces registry or version-based dependencies with explicit path dependencies pointing to the local repository. It writes the changes directly to your manifest files.
Restart RustRover after the command finishes. The IDE will trigger a full reindex. Your breakpoints will suddenly resolve. The call stack will show readable Rust code instead of mangled symbols.
What happens under the hood
The cargo dev setup intellij command does not install a plugin or modify your IDE configuration. It rewrites your build graph. Before the command runs, your Cargo.toml might look like this:
[dependencies]
rustc_driver = { version = "0.0.0", package = "rustc_driver" }
rustc_middle = { version = "0.0.0", package = "rustc_middle" }
After the command finishes, the manifest shifts to explicit local paths:
[dependencies]
# Point directly at the local compiler checkout
# This gives the IDE a concrete directory to index
rustc_driver = { path = "/absolute/path/to/rust-lang/rust/compiler/rustc_driver" }
rustc_middle = { path = "/absolute/path/to/rust-lang/rust/compiler/rustc_middle" }
The IDE's cargo integration reads the path field and immediately knows where to look. It walks those directories, finds the .rs files, and builds a symbol table. The debugger gains the exact file offsets it needs to pause execution at your breakpoints.
The command also handles workspace members. If your project contains multiple crates that depend on compiler internals, the tool patches every manifest in the workspace. You do not need to run it per crate. One execution covers the entire tree.
Convention aside: keep your rust-lang/rust checkout on a fast drive. The IDE indexes thousands of files. An SSD cuts reindex time from minutes to seconds. The community treats this as a baseline requirement for compiler development.
Restart the IDE after every manifest change. IntelliJ caches dependency graphs aggressively. It will not pick up path updates until you force a refresh.
Setting up your first debug session
Once the IDE recognizes the source paths, you need a run configuration that actually compiles and launches the compiler. RustRover does not guess how to build rustc. You must tell it which package to compile and which arguments to pass.
Create a new Run Configuration. Select Cargo as the type. Set the package to rustc_driver or whichever crate contains your entry point. Add the --package flag to the command line. If you are debugging a specific test, add --test and the test name.
# Example Cargo.toml snippet for a compiler plugin
# The path dependencies are already resolved by the setup command
[package]
name = "my-compiler-plugin"
version = "0.1.0"
edition = "2021"
[dependencies]
# Local paths enable IDE navigation and debugging
rustc_driver = { path = "/absolute/path/to/rust-lang/rust/compiler/rustc_driver" }
Attach a breakpoint inside the crate you are investigating. Run the configuration. The debugger will compile the compiler, launch the process, and pause at your breakpoint. The variables view will show hir::Item, ty::Ty, and other compiler-specific types. You can step through macro expansion, type checking, and borrow analysis.
Convention aside: use File > Invalidate Caches if the IDE shows stale symbols after a successful setup. The indexer occasionally holds onto old metadata. A cache clear forces a clean slate. It takes a minute but saves hours of confusion.
When the setup fights back
The workflow is straightforward, but a few friction points appear regularly.
Path mismatches are the most common failure. If you move the rust-lang/rust directory after running the setup command, the paths in your Cargo.toml become stale. The IDE will show red squiggles on imports. The debugger will refuse to attach. Run the setup command again with the new path. Do not manually edit the paths. The tool handles cross-references and workspace consistency better than hand-editing.
Repository state breaks indexing. If your local compiler checkout is on a detached HEAD, or if you switch branches without rebuilding, the IDE may index mismatched source files. The debugger will pause at the wrong line. Always run git checkout followed by the setup command when switching compiler branches.
IDE memory limits can stall the index. The compiler source tree contains hundreds of thousands of lines. IntelliJ's default heap allocation sometimes chokes on the initial scan. Increase the IDE memory in Help > Change Memory Settings to at least 4096 MB. The indexer will finish without freezing your machine.
Compiler errors will still appear in the terminal. The IDE setup does not bypass rustc validation. If your code violates trait bounds or moves values incorrectly, you will see E0277 or E0382 in the build output. The debugger only helps after compilation succeeds.
Keep your manifests clean. Commit the path dependencies to a separate development branch or use .gitignore for local overrides. Shipping absolute paths to a shared repository breaks builds for everyone else.
Picking your debugging strategy
Different problems require different tools. Compiler development sits at the edge of application code and systems programming. Match your debugging approach to the scope of the task.
Use cargo dev setup intellij when you are debugging compiler internals, macro expansion, or trait resolution logic. Use standard cargo run debugging when you are working on application code that does not depend on rustc crates. Use println! or dbg! when you are on a remote machine, in a CI pipeline, or when the IDE setup overhead outweighs the benefit. Use gdb or lldb with rust-gdb when you need low-level memory inspection, thread state analysis, or stack unwinding that the IDE debugger does not expose.
Trust the path dependencies. They are the bridge between your code and the compiler's source. Keep them accurate.