Skip to content

Commit

Permalink
Rework the driver docs
Browse files Browse the repository at this point in the history
The removal of driver queries has changed the way rustc_interface can be
used. I've also changed things a bit to fix things that were wrong in
the docs even before the driver query removal. And finally this is now
recommends using rustc_driver over rustc_interface like most tools are
already doing. The rustc_driver api is a bit more stable and much easier
to use when you don't need the flexibility of rustc_interface as is the
case almost all the time. Rustdoc is one of the few tools that needs the
flexibility of rustc_interface.
  • Loading branch information
bjorn3 committed Dec 15, 2024
1 parent e7dd865 commit b4188ed
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 184 deletions.
139 changes: 67 additions & 72 deletions examples/rustc-driver-example.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![feature(rustc_private)]

extern crate rustc_ast_pretty;
extern crate rustc_driver;
extern crate rustc_error_codes;
extern crate rustc_errors;
Expand All @@ -9,84 +10,78 @@ extern crate rustc_interface;
extern crate rustc_session;
extern crate rustc_span;

use std::{path, process, str, sync::Arc};
use std::io;
use std::path::Path;
use std::str;
use std::sync::Arc;

use rustc_ast_pretty::pprust::item_to_string;
use rustc_data_structures::sync::Lrc;
use rustc_driver::Compilation;
use rustc_errors::registry;
use rustc_hash::FxHashMap;
use rustc_session::config;

struct MyFileLoader;

impl rustc_span::source_map::FileLoader for MyFileLoader {
fn file_exists(&self, path: &Path) -> bool {
path == "main.rs"
}

fn read_file(&self, path: &Path) -> io::Result<String> {
if path == "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(
_compiler: &rustc_interface::Compiler,
krate: &rustc_ast::Crate,
) -> Compilation {
for item in krate.items {
println!("{}", item_to_string(&item));
}

Compilation::Continue
}

fn after_analysis(_compiler: &rustc_interface::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() {
RunCompiler::new(&["main.rs".to_string()], MyCallbacks)
.set_file_loader(Some(Box::new(MyFileLoader)))
.run();
}
132 changes: 68 additions & 64 deletions examples/rustc-driver-interacting-with-the-ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,81 +10,85 @@ extern crate rustc_interface;
extern crate rustc_session;
extern crate rustc_span;

use std::{path, process, str, sync::Arc};
use std::io;
use std::path::Path;
use std::str;
use std::sync::Arc;

use rustc_ast_pretty::pprust::item_to_string;
use rustc_data_structures::sync::Lrc;
use rustc_driver::Compilation;
use rustc_errors::registry;
use rustc_session::config;

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 == "main.rs"
}

fn read_file(&self, path: &Path) -> io::Result<String> {
if path == "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(
_compiler: &rustc_interface::Compiler,
krate: &rustc_ast::Crate,
) -> Compilation {
for item in krate.items {
println!("{}", item_to_string(&item));
}

Compilation::Continue
}

fn after_analysis(_compiler: &rustc_interface::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() {
RunCompiler::new(&["main.rs".to_string()], MyCallbacks)
.set_file_loader(Some(Box::new(MyFileLoader)))
.run();
}
81 changes: 81 additions & 0 deletions examples/rustc-interface-example.rs
Original file line number Diff line number Diff line change
@@ -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::{path, str, 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,
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 parse = rustc_interface::passes::parse(&compiler.sess);
println!("{parse:?}");
// 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:?}")
}
_ => (),
}
}
});
});
}
Loading

0 comments on commit b4188ed

Please sign in to comment.