Rework the driver docs (#2162)

This commit is contained in:
bjorn3 2024-12-27 13:40:32 +01:00 committed by GitHub
parent 4e746875ed
commit e70f551db6
7 changed files with 273 additions and 199 deletions

View File

@ -1,92 +1,92 @@
#![feature(rustc_private)]
extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_error_codes;
extern crate rustc_errors;
extern crate rustc_hash;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;
use std::{path, process, str, sync::Arc};
use std::io;
use std::path::Path;
use rustc_errors::registry;
use rustc_hash::FxHashMap;
use rustc_session::config;
use rustc_ast_pretty::pprust::item_to_string;
use rustc_data_structures::sync::Lrc;
use rustc_driver::{Compilation, RunCompiler};
use rustc_interface::interface::Compiler;
use rustc_middle::ty::TyCtxt;
struct MyFileLoader;
impl rustc_span::source_map::FileLoader for MyFileLoader {
fn file_exists(&self, path: &Path) -> bool {
path == Path::new("main.rs")
}
fn read_file(&self, path: &Path) -> io::Result<String> {
if path == Path::new("main.rs") {
Ok(r#"
fn main() {
let out = process::Command::new("rustc")
.arg("--print=sysroot")
.current_dir(".")
.output()
.unwrap();
let sysroot = str::from_utf8(&out.stdout).unwrap().trim();
let config = rustc_interface::Config {
// Command line options
opts: config::Options {
maybe_sysroot: Some(path::PathBuf::from(sysroot)),
..config::Options::default()
},
// cfg! configuration in addition to the default ones
crate_cfg: Vec::new(), // FxHashSet<(String, Option<String>)>
crate_check_cfg: Vec::new(), // CheckCfg
input: config::Input::Str {
name: rustc_span::FileName::Custom("main.rs".into()),
input: r#"
static HELLO: &str = "Hello, world!";
fn main() {
println!("{HELLO}");
let message = "Hello, World!";
println!("{message}");
}
"#
.into(),
},
output_dir: None, // Option<PathBuf>
output_file: None, // Option<PathBuf>
file_loader: None, // Option<Box<dyn FileLoader + Send + Sync>>
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
lint_caps: FxHashMap::default(), // FxHashMap<lint::LintId, lint::Level>
// This is a callback from the driver that is called when [`ParseSess`] is created.
psess_created: None, //Option<Box<dyn FnOnce(&mut ParseSess) + Send>>
// This is a callback from the driver that is called when we're registering lints;
// it is called during plugin registration when we have the LintStore in a non-shared state.
//
// Note that if you find a Some here you probably want to call that function in the new
// function being registered.
register_lints: None, // Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>
// This is a callback from the driver that is called just after we have populated
// the list of queries.
//
// The second parameter is local providers and the third parameter is external providers.
override_queries: None, // Option<fn(&Session, &mut ty::query::Providers<'_>, &mut ty::query::Providers<'_>)>
// Registry of diagnostics codes.
registry: registry::Registry::new(rustc_errors::codes::DIAGNOSTICS),
make_codegen_backend: None,
expanded_args: Vec::new(),
ice_file: None,
hash_untracked_state: None,
using_internal_features: Arc::default(),
};
rustc_interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
// Parse the program and print the syntax tree.
let parse = queries.parse().unwrap().get_mut().clone();
println!("{parse:?}");
// Analyze the program and inspect the types of definitions.
queries.global_ctxt().unwrap().enter(|tcx| {
for id in tcx.hir().items() {
let hir = tcx.hir();
let item = hir.item(id);
match item.kind {
rustc_hir::ItemKind::Static(_, _, _) | rustc_hir::ItemKind::Fn(_, _, _) => {
let name = item.ident;
let ty = tcx.type_of(item.hir_id().owner.def_id);
println!("{name:?}:\t{ty:?}")
}
_ => (),
}
}
})
});
});
.to_string())
} else {
Err(io::Error::other("oops"))
}
}
fn read_binary_file(&self, _path: &Path) -> io::Result<Lrc<[u8]>> {
Err(io::Error::other("oops"))
}
}
struct MyCallbacks;
impl rustc_driver::Callbacks for MyCallbacks {
fn after_crate_root_parsing(
&mut self,
_compiler: &Compiler,
krate: &rustc_ast::Crate,
) -> Compilation {
for item in &krate.items {
println!("{}", item_to_string(&item));
}
Compilation::Continue
}
fn after_analysis(&mut self, _compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
// Analyze the program and inspect the types of definitions.
for id in tcx.hir().items() {
let hir = tcx.hir();
let item = hir.item(id);
match item.kind {
rustc_hir::ItemKind::Static(_, _, _) | rustc_hir::ItemKind::Fn(_, _, _) => {
let name = item.ident;
let ty = tcx.type_of(item.hir_id().owner.def_id);
println!("{name:?}:\t{ty:?}")
}
_ => (),
}
}
Compilation::Stop
}
}
fn main() {
match RunCompiler::new(&["main.rs".to_string()], &mut MyCallbacks) {
mut compiler => {
compiler.set_file_loader(Some(Box::new(MyFileLoader)));
compiler.run();
}
}
}

View File

@ -1,90 +1,99 @@
#![feature(rustc_private)]
extern crate rustc_ast;
extern crate rustc_ast_pretty;
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_error_codes;
extern crate rustc_errors;
extern crate rustc_hash;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;
use std::{path, process, str, sync::Arc};
use std::io;
use std::path::Path;
use rustc_ast_pretty::pprust::item_to_string;
use rustc_errors::registry;
use rustc_session::config;
use rustc_data_structures::sync::Lrc;
use rustc_driver::{Compilation, RunCompiler};
use rustc_interface::interface::Compiler;
use rustc_middle::ty::TyCtxt;
fn main() {
let out = process::Command::new("rustc")
.arg("--print=sysroot")
.current_dir(".")
.output()
.unwrap();
let sysroot = str::from_utf8(&out.stdout).unwrap().trim();
let config = rustc_interface::Config {
opts: config::Options {
maybe_sysroot: Some(path::PathBuf::from(sysroot)),
..config::Options::default()
},
input: config::Input::Str {
name: rustc_span::FileName::Custom("main.rs".to_string()),
input: r#"
struct MyFileLoader;
impl rustc_span::source_map::FileLoader for MyFileLoader {
fn file_exists(&self, path: &Path) -> bool {
path == Path::new("main.rs")
}
fn read_file(&self, path: &Path) -> io::Result<String> {
if path == Path::new("main.rs") {
Ok(r#"
fn main() {
let message = "Hello, World!";
println!("{message}");
}
"#
.to_string(),
},
crate_cfg: Vec::new(),
crate_check_cfg: Vec::new(),
output_dir: None,
output_file: None,
file_loader: None,
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
lint_caps: rustc_hash::FxHashMap::default(),
psess_created: None,
register_lints: None,
override_queries: None,
make_codegen_backend: None,
registry: registry::Registry::new(rustc_errors::codes::DIAGNOSTICS),
expanded_args: Vec::new(),
ice_file: None,
hash_untracked_state: None,
using_internal_features: Arc::default(),
};
rustc_interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
// TODO: add this to -Z unpretty
let ast_krate = queries.parse().unwrap().get_mut().clone();
for item in ast_krate.items {
println!("{}", item_to_string(&item));
}
// Analyze the crate and inspect the types under the cursor.
queries.global_ctxt().unwrap().enter(|tcx| {
// Every compilation contains a single crate.
let hir_krate = tcx.hir();
// Iterate over the top-level items in the crate, looking for the main function.
for id in hir_krate.items() {
let item = hir_krate.item(id);
// Use pattern-matching to find a specific node inside the main function.
if let rustc_hir::ItemKind::Fn(_, _, body_id) = item.kind {
let expr = &tcx.hir().body(body_id).value;
if let rustc_hir::ExprKind::Block(block, _) = expr.kind {
if let rustc_hir::StmtKind::Let(let_stmt) = block.stmts[0].kind {
if let Some(expr) = let_stmt.init {
let hir_id = expr.hir_id; // hir_id identifies the string "Hello, world!"
let def_id = item.hir_id().owner.def_id; // def_id identifies the main function
let ty = tcx.typeck(def_id).node_type(hir_id);
println!("{expr:#?}: {ty:?}");
}
}
.to_string())
} else {
Err(io::Error::other("oops"))
}
}
fn read_binary_file(&self, _path: &Path) -> io::Result<Lrc<[u8]>> {
Err(io::Error::other("oops"))
}
}
struct MyCallbacks;
impl rustc_driver::Callbacks for MyCallbacks {
fn after_crate_root_parsing(
&mut self,
_compiler: &Compiler,
krate: &rustc_ast::Crate,
) -> Compilation {
for item in &krate.items {
println!("{}", item_to_string(&item));
}
Compilation::Continue
}
fn after_analysis(&mut self, _compiler: &Compiler, tcx: TyCtxt<'_>) -> Compilation {
// Every compilation contains a single crate.
let hir_krate = tcx.hir();
// Iterate over the top-level items in the crate, looking for the main function.
for id in hir_krate.items() {
let item = hir_krate.item(id);
// Use pattern-matching to find a specific node inside the main function.
if let rustc_hir::ItemKind::Fn(_, _, body_id) = item.kind {
let expr = &tcx.hir().body(body_id).value;
if let rustc_hir::ExprKind::Block(block, _) = expr.kind {
if let rustc_hir::StmtKind::Let(let_stmt) = block.stmts[0].kind {
if let Some(expr) = let_stmt.init {
let hir_id = expr.hir_id; // hir_id identifies the string "Hello, world!"
let def_id = item.hir_id().owner.def_id; // def_id identifies the main function
let ty = tcx.typeck(def_id).node_type(hir_id);
println!("{expr:#?}: {ty:?}");
}
}
}
})
});
});
}
}
Compilation::Stop
}
}
fn main() {
match RunCompiler::new(&["main.rs".to_string()], &mut MyCallbacks) {
mut compiler => {
compiler.set_file_loader(Some(Box::new(MyFileLoader)));
compiler.run();
}
}
}

View File

@ -0,0 +1,81 @@
#![feature(rustc_private)]
extern crate rustc_driver;
extern crate rustc_error_codes;
extern crate rustc_errors;
extern crate rustc_hash;
extern crate rustc_hir;
extern crate rustc_interface;
extern crate rustc_session;
extern crate rustc_span;
use std::sync::Arc;
use rustc_errors::registry;
use rustc_hash::FxHashMap;
use rustc_session::config;
fn main() {
let config = rustc_interface::Config {
// Command line options
opts: config::Options::default(),
// cfg! configuration in addition to the default ones
crate_cfg: Vec::new(), // FxHashSet<(String, Option<String>)>
crate_check_cfg: Vec::new(), // CheckCfg
input: config::Input::Str {
name: rustc_span::FileName::Custom("main.rs".into()),
input: r#"
static HELLO: &str = "Hello, world!";
fn main() {
println!("{HELLO}");
}
"#
.into(),
},
output_dir: None, // Option<PathBuf>
output_file: None, // Option<PathBuf>
file_loader: None, // Option<Box<dyn FileLoader + Send + Sync>>
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES.to_owned(),
lint_caps: FxHashMap::default(), // FxHashMap<lint::LintId, lint::Level>
// This is a callback from the driver that is called when [`ParseSess`] is created.
psess_created: None, //Option<Box<dyn FnOnce(&mut ParseSess) + Send>>
// This is a callback from the driver that is called when we're registering lints;
// it is called during plugin registration when we have the LintStore in a non-shared state.
//
// Note that if you find a Some here you probably want to call that function in the new
// function being registered.
register_lints: None, // Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>
// This is a callback from the driver that is called just after we have populated
// the list of queries.
//
// The second parameter is local providers and the third parameter is external providers.
override_queries: None, // Option<fn(&Session, &mut ty::query::Providers<'_>, &mut ty::query::Providers<'_>)>
// Registry of diagnostics codes.
registry: registry::Registry::new(rustc_errors::codes::DIAGNOSTICS),
make_codegen_backend: None,
expanded_args: Vec::new(),
ice_file: None,
hash_untracked_state: None,
using_internal_features: Arc::default(),
};
rustc_interface::run_compiler(config, |compiler| {
// Parse the program and print the syntax tree.
let krate = rustc_interface::passes::parse(&compiler.sess);
println!("{krate:?}");
// Analyze the program and inspect the types of definitions.
rustc_interface::create_and_enter_global_ctxt(&compiler, krate, |tcx| {
for id in tcx.hir().items() {
let hir = tcx.hir();
let item = hir.item(id);
match item.kind {
rustc_hir::ItemKind::Static(_, _, _) | rustc_hir::ItemKind::Fn(_, _, _) => {
let name = item.ident;
let ty = tcx.type_of(item.hir_id().owner.def_id);
println!("{name:?}:\t{ty:?}")
}
_ => (),
}
}
});
});
}

View File

@ -1,5 +1,6 @@
#![feature(rustc_private)]
extern crate rustc_data_structures;
extern crate rustc_driver;
extern crate rustc_error_codes;
extern crate rustc_errors;
@ -9,16 +10,14 @@ extern crate rustc_interface;
extern crate rustc_session;
extern crate rustc_span;
use rustc_errors::{
emitter::Emitter, registry, translation::Translate, DiagCtxt, DiagInner, FluentBundle,
};
use rustc_errors::emitter::Emitter;
use rustc_errors::registry::{self, Registry};
use rustc_errors::translation::Translate;
use rustc_errors::{DiagCtxt, DiagInner, FluentBundle};
use rustc_session::config;
use rustc_span::source_map::SourceMap;
use std::{
path, process, str,
sync::{Arc, Mutex},
};
use std::sync::{Arc, Mutex};
struct DebugEmitter {
source_map: Arc<SourceMap>,
@ -26,7 +25,7 @@ struct DebugEmitter {
}
impl Translate for DebugEmitter {
fn fluent_bundle(&self) -> Option<&Arc<FluentBundle>> {
fn fluent_bundle(&self) -> Option<&FluentBundle> {
None
}
@ -36,29 +35,20 @@ impl Translate for DebugEmitter {
}
impl Emitter for DebugEmitter {
fn emit_diagnostic(&mut self, diag: DiagInner) {
fn emit_diagnostic(&mut self, diag: DiagInner, _: &Registry) {
self.diagnostics.lock().unwrap().push(diag);
}
fn source_map(&self) -> Option<&Arc<SourceMap>> {
fn source_map(&self) -> Option<&SourceMap> {
Some(&self.source_map)
}
}
fn main() {
let out = process::Command::new("rustc")
.arg("--print=sysroot")
.current_dir(".")
.output()
.unwrap();
let sysroot = str::from_utf8(&out.stdout).unwrap().trim();
let buffer: Arc<Mutex<Vec<DiagInner>>> = Arc::default();
let diagnostics = buffer.clone();
let config = rustc_interface::Config {
opts: config::Options {
maybe_sysroot: Some(path::PathBuf::from(sysroot)),
..config::Options::default()
},
opts: config::Options::default(),
// This program contains a type error.
input: config::Input::Str {
name: rustc_span::FileName::Custom("main.rs".into()),
@ -74,7 +64,7 @@ fn main() {
output_dir: None,
output_file: None,
file_loader: None,
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES,
locale_resources: rustc_driver::DEFAULT_LOCALE_RESOURCES.to_owned(),
lint_caps: rustc_hash::FxHashMap::default(),
psess_created: Some(Box::new(|parse_sess| {
parse_sess.set_dcx(DiagCtxt::new(Box::new(DebugEmitter {
@ -92,11 +82,10 @@ fn main() {
using_internal_features: Arc::default(),
};
rustc_interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
queries.global_ctxt().unwrap().enter(|tcx| {
// Run the analysis phase on the local crate to trigger the type error.
let _ = tcx.analysis(());
});
let krate = rustc_interface::passes::parse(&compiler.sess);
rustc_interface::create_and_enter_global_ctxt(&compiler, krate, |tcx| {
// Run the analysis phase on the local crate to trigger the type error.
let _ = tcx.analysis(());
});
// If the compiler has encountered errors when this closure returns, it will abort (!) the program.
// We avoid this by resetting the error count before returning

View File

@ -11,7 +11,7 @@ and run [`TyCtxt.analysis`].
The following was tested with <!-- date-check: september 2024 --> `nightly-2024-09-16`:
```rust
{{#include ../../examples/rustc-driver-getting-diagnostics.rs}}
{{#include ../../examples/rustc-interface-getting-diagnostics.rs}}
```
[`rustc_interface`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_interface/index.html

View File

@ -1,16 +1,15 @@
# Example: Type checking through `rustc_interface`
# Example: Type checking through `rustc_driver`
The [`rustc_interface`] allows you to interact with Rust code at various stages of compilation.
[`rustc_driver`] allows you to interact with Rust code at various stages of compilation.
## Getting the type of an expression
To get the type of an expression, use the [`global_ctxt`] query to [get] a [`TyCtxt`].
The following was tested with <!-- date-check: may 2024 --> `nightly-2024-05-09`:
To get the type of an expression, use the [`after_analysis`] callback to get a [`TyCtxt`].
The following was tested with <!-- date-check: december 2024 --> `nightly-2024-12-15`:
```rust
{{#include ../../examples/rustc-driver-interacting-with-the-ast.rs}}
```
[get]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.GlobalCtxt.html#method.enter
[`global_ctxt`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_interface/queries/struct.Queries.html#method.global_ctxt
[`rustc_interface`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_interface
[`after_analysis`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/trait.Callbacks.html#method.after_analysis
[`rustc_driver`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver
[`TyCtxt`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html

View File

@ -1,35 +1,30 @@
# `rustc_driver` and `rustc_interface`
## `rustc_driver`
The [`rustc_driver`] is essentially `rustc`'s `main` function.
It acts as the glue for running the various phases of the compiler in the correct order,
using the interface defined in the [`rustc_interface`] crate.
using the interface defined in the [`rustc_interface`] crate. Where possible, using [`rustc_driver`] rather than [`rustc_interface`] is recommended.
Generally the [`rustc_interface`] crate provides external users with an (unstable) API
for running code at particular times during the compilation process, allowing
third parties to effectively use `rustc`'s internals as a library for
analyzing a crate or for ad hoc emulating of the compiler (i.e. `rustdoc`
compiling code and serving output).
The main entry point of [`rustc_driver`] is [`rustc_driver::RunCompiler`][rd_rc].
This builder accepts the same command-line args as rustc as well as an implementation of [`Callbacks`][cb] and a couple of other optional options.
[`Callbacks`][cb] is a `trait` that allows for custom compiler configuration,
as well as allowing custom code to run after different phases of the compilation.
More specifically the [`rustc_interface::run_compiler`][i_rc] function is the
main entrypoint for using [`nightly-rustc`] as a library.
Initially [`run_compiler`][i_rc] takes a configuration variable for the compiler
## `rustc_interface`
The [`rustc_interface`] crate provides a low level API to external users for manually driving the compilation process,
allowing third parties to effectively use `rustc`'s internals as a library for analyzing a crate or for ad hoc emulating of the compiler for cases where [`rustc_driver`] is not flexible enough (i.e. `rustdoc` compiling code and serving output).
The main entry point of [`rustc_interface`] ([`rustc_interface::run_compiler`][i_rc]) takes a configuration variable for the compiler
and a `closure` taking a yet unresolved [`Compiler`].
Operationally [`run_compiler`][i_rc] creates a `Compiler` from the configuration and passes
it to the `closure`. Inside the `closure` you can use the `Compiler` to drive
queries to compile a crate and get the results.
Providing results about the internal state of the compiler what the [`rustc_driver`] does too.
[`run_compiler`][i_rc] creates a `Compiler` from the configuration and passes it to the `closure`.
Inside the `closure` you can use the `Compiler` to call various functions to compile a crate and get the results.
You can see a minimal example of how to use [`rustc_interface`] [here][example].
You can see what queries are currently available in the [`Compiler`] rustdocs.
You can see an example of how to use the queries by looking at the `rustc_driver` implementation,
specifically [`rustc_driver::run_compiler`][rd_rc]
You can see an example of how to use the various functions using [`rustc_interface`] needs by looking at the `rustc_driver` implementation,
specifically [`rustc_driver_impl::run_compiler`][rdi_rc]
(not to be confused with [`rustc_interface::run_compiler`][i_rc]).
Generally [`rustc_driver::run_compiler`][i_rc] takes a bunch of command-line args
and some other configurations and drives the compilation to completion.
Finally [`rustc_driver::run_compiler`][rd_rc] also takes a [`Callbacks`][cb],
which is a `trait` that allows for custom compiler configuration,
as well as allowing custom code to run after different phases of the compilation.
> **Warning:** By its very nature, the internal compiler APIs are always going
> to be unstable. That said, we do try not to break things unnecessarily.
@ -43,8 +38,9 @@ as well as allowing custom code to run after different phases of the compilation
[`TyCtxt`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TyCtxt.html
[Appendix A]: appendix/stupid-stats.html
[cb]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/trait.Callbacks.html
[example]: https://github.com/rust-lang/rustc-dev-guide/blob/master/examples/rustc-driver-example.rs
[example]: https://github.com/rust-lang/rustc-dev-guide/blob/master/examples/rustc-interface-example.rs
[i_rc]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_interface/interface/fn.run_compiler.html
[rd_rc]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver_impl/fn.run_compiler.html
[rd_rc]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver/struct.RunCompiler.html
[rdi_rc]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_driver_impl/fn.run_compiler.html
[stupid-stats]: https://github.com/nrc/stupid-stats
[`nightly-rustc`]: https://doc.rust-lang.org/nightly/nightly-rustc/