blob: 6d8b964040d6e1264ca5064d73e1bc4972102444 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
use anyhow::{anyhow, bail, Context, Result};
use code_gen_utils::format_cc_ident;
use proc_macro2::TokenStream;
use quote::quote;
use rustc_hir::{Item, ItemKind, Node, Unsafety};
use rustc_interface::Queries;
use rustc_middle::dep_graph::DepContext;
use rustc_middle::middle::exported_symbols::ExportedSymbol;
use rustc_middle::ty::{Ty, TyCtxt};
use rustc_span::def_id::{LocalDefId, LOCAL_CRATE};
use rustc_span::symbol::Ident;
use rustc_target::spec::abi::Abi;
pub struct GeneratedBindings {
pub h_body: TokenStream,
}
impl GeneratedBindings {
pub fn generate(tcx: TyCtxt) -> Self {
let top_comment = {
let crate_name = tcx.crate_name(LOCAL_CRATE);
let txt = format!(
"Automatically @generated C++ bindings for the following Rust crate:\n\
{crate_name}"
);
quote! { __COMMENT__ #txt __NEWLINE__ }
};
let h_body = {
let crate_content = format_crate(tcx).unwrap_or_else(|err| {
let txt = format!("Failed to generate bindings for the crate: {}", err);
quote! { __COMMENT__ #txt }
});
// TODO(b/251445877): Replace `#pragma once` with include guards.
quote! {
#top_comment
__HASH_TOKEN__ pragma once __NEWLINE__
__NEWLINE__
#crate_content
}
};
Self { h_body }
}
}
/// Helper (used by `bindings_driver` and `test::run_compiler`) for invoking
/// functions operating on `TyCtxt`.
pub fn enter_tcx<'tcx, F, T>(
queries: &'tcx Queries<'tcx>,
f: F,
) -> rustc_interface::interface::Result<T>
where
F: FnOnce(TyCtxt<'tcx>) -> T + Send,
T: Send,
{
let query_context = queries.global_ctxt()?;
Ok(query_context.peek_mut().enter(f))
}
fn format_ty(ty: Ty) -> Result<TokenStream> {
if ty.is_unit() {
Ok(quote! { void })
} else {
bail!("The following Rust type is not supported yet: {}", ty)
}
}
/// Formats a function with the given `def_id` and `fn_name`.
///
/// Will panic if `def_id` is invalid or doesn't identify a function.
fn format_fn(tcx: TyCtxt, def_id: LocalDefId, fn_name: &Ident) -> Result<TokenStream> {
let sig = tcx
.fn_sig(def_id.to_def_id())
.no_bound_vars()
.expect("Caller (e.g. `format_def`) should verify no unbound generic vars");
if sig.c_variadic {
// TODO(b/254097223): Add support for variadic functions.
bail!("C variadic functions are not supported (b/254097223)");
}
if sig.inputs().len() != 0 {
// TODO(lukasza): Add support for function parameters.
bail!("Function parameters are not supported yet");
}
match sig.unsafety {
Unsafety::Normal => (),
Unsafety::Unsafe => {
// TODO(b/254095482): Figure out how to handle `unsafe` functions.
bail!("Bindings for `unsafe` functions are not fully designed yet (b/254095482)");
}
}
let need_thunk = match sig.abi {
// Before https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that
// "escapes" a "C" ABI function leads to Undefined Behavior. This is unfortunate,
// but Crubit's `panics_and_exceptions.md` documents that `-Cpanic=abort` is the
// only supported configuration.
//
// After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a Rust panic that
// tries to "escape" a "C" ABI function will terminate the program. This is okay.
Abi::C { unwind: false } => false,
// After https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html a new "C-unwind" ABI
// may be used by Rust functions that want to safely propagate Rust panics through
// frames that may belong to another language.
Abi::C { unwind: true } => false,
// In all other cases, C++ needs to call into a Rust thunk that wraps the original function
// in a "C" ABI.
_ => true,
};
if need_thunk {
// TODO(b/254097223): Add support for Rust thunks.
bail!(
"Functions that require Rust thunks (e.g. non-`extern \"C\"`) are not supported yet \
(b/254097223)"
);
}
let ret_type = format_ty(sig.output()).context("Error formatting function return type")?;
let fn_name = format_cc_ident(fn_name.as_str()).context("Error formatting function name")?;
Ok(quote! {
extern "C" #ret_type #fn_name ();
})
}
/// Formats a Rust item idenfied by `def_id`.
///
/// Will panic if `def_id` is invalid (i.e. doesn't identify a Rust node or
/// item).
fn format_def(tcx: TyCtxt, def_id: LocalDefId) -> Result<TokenStream> {
match tcx.hir().get_by_def_id(def_id) {
Node::Item(item) => match item {
Item { ident, kind: ItemKind::Fn(_hir_fn_sig, generics, _body), .. } => {
if generics.params.len() == 0 {
format_fn(tcx, def_id, &ident)
} else {
bail!(
"Generic functions (lifetime-generic or type-generic) are not supported yet"
)
}
}
Item { kind, .. } => bail!("Unsupported rustc_hir::hir::ItemKind: {}", kind.descr()),
},
_unsupported_node => bail!("Unsupported rustc_hir::hir::Node"),
}
}
/// Formats a C++ comment explaining why no bindings have been generated for
/// `local_def_id`.
fn format_unsupported_def(
tcx: TyCtxt,
local_def_id: LocalDefId,
err: anyhow::Error,
) -> TokenStream {
let span = tcx.sess().source_map().span_to_embeddable_string(tcx.def_span(local_def_id));
let name = tcx.def_path_str(local_def_id.to_def_id());
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations
// says: To print causes as well [...], use the alternate selector “{:#}”.
let msg = format!("Error generating bindings for `{name}` defined at {span}: {err:#}");
quote! { __NEWLINE__ __NEWLINE__ __COMMENT__ #msg __NEWLINE__ }
}
/// Formats all public items from the Rust crate being compiled (aka the
/// `LOCAL_CRATE`).
fn format_crate(tcx: TyCtxt) -> Result<TokenStream> {
let crate_name = format_cc_ident(tcx.crate_name(LOCAL_CRATE).as_str())?;
// TODO(lukasza): We probably shouldn't be using `exported_symbols` as the main
// entry point for finding Rust definitions that need to be wrapping in C++
// bindings. For example, it _seems_ that things like `type` aliases or
// `struct`s (without an `impl`) won't be visible to a linker and therefore
// won't have exported symbols. Additionally, walking Rust's modules top-down
// might result in easier translation into C++ namespaces.
let snippets =
tcx.exported_symbols(LOCAL_CRATE).iter().filter_map(move |(symbol, _)| match symbol {
ExportedSymbol::NonGeneric(def_id) => {
// It seems that non-generic exported symbols should all be defined in the
// `LOCAL_CRATE`. Furthermore, `def_id` seems to be a `LocalDefId`. OTOH, it
// isn't clear why `ExportedSymbol::NonGeneric` holds a `DefId` rather than a
// `LocalDefId`. For now, we assert `expect_local` below (and if it fails, then
// hopefully it will help us understand these things better and maybe add
// extra unit tests against out code).
let local_id = def_id.expect_local();
Some(match format_def(tcx, local_id) {
Ok(snippet) => snippet,
Err(err) => format_unsupported_def(tcx, local_id, err),
})
}
ExportedSymbol::Generic(def_id, _substs) => {
// Ignore non-local defs. Map local defs to an unsupported comment.
//
// We are guessing that a non-local `def_id` can happen when the `LOCAL_CRATE`
// exports a monomorphization/specialization of a generic defined in a different
// crate. One specific example (covered via `async fn` in one of the tests) is
// `DefId(2:14250 ~ core[ef75]::future::from_generator)`.
def_id.as_local().map(|local_id| {
format_unsupported_def(tcx, local_id, anyhow!("Generics are not supported yet."))
})
}
ExportedSymbol::DropGlue(_) | ExportedSymbol::NoDefId(_) => None,
});
Ok(quote! {
namespace #crate_name {
#( #snippets )*
}
})
}
#[cfg(test)]
pub mod tests {
use super::{format_def, GeneratedBindings};
use anyhow::Result;
use itertools::Itertools;
use proc_macro2::TokenStream;
use quote::quote;
use rustc_middle::ty::TyCtxt;
use rustc_span::def_id::LocalDefId;
use std::path::PathBuf;
use token_stream_matchers::{assert_cc_matches, assert_cc_not_matches};
pub fn get_sysroot_for_testing() -> PathBuf {
let runfiles = runfiles::Runfiles::create().unwrap();
runfiles.rlocation(if std::env::var("LEGACY_TOOLCHAIN_RUST_TEST").is_ok() {
"google3/third_party/unsupported_toolchains/rust/toolchains/nightly"
} else {
"google3/nowhere/llvm/rust"
})
}
#[test]
#[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
fn test_infra_panic_when_test_input_contains_syntax_errors() {
run_compiler("syntax error here", |_tcx| panic!("This part shouldn't execute"))
}
#[test]
#[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
fn test_infra_panic_when_test_input_triggers_analysis_errors() {
run_compiler("#![feature(no_such_feature)]", |_tcx| panic!("This part shouldn't execute"))
}
#[test]
#[should_panic(expected = "Test inputs shouldn't cause compilation errors")]
fn test_infra_panic_when_test_input_triggers_warnings() {
run_compiler("pub fn foo(unused_parameter: i32) {}", |_tcx| {
panic!("This part shouldn't execute")
})
}
#[test]
fn test_infra_nightly_features_ok_in_test_input() {
// This test arbitrarily picks `yeet_expr` as an example of a feature that
// hasn't yet been stabilized.
let test_src = r#"
// This test is supposed to test that *nightly* features are ok
// in the test input. The `forbid` directive below helps to
// ensure that we'll realize in the future when the `yeet_expr`
// feature gets stabilized, making it not quite fitting for use
// in this test.
#![forbid(stable_features)]
#![feature(yeet_expr)]
"#;
run_compiler(test_src, |_tcx| ())
}
#[test]
fn test_infra_stabilized_features_ok_in_test_input() {
// This test arbitrarily picks `const_ptr_offset_from` as an example of a
// feature that has been already stabilized.
run_compiler("#![feature(const_ptr_offset_from)]", |_tcx| ())
}
#[test]
#[should_panic(expected = "No items named `missing_name`.\n\
Instead found:\n`bar`,\n`foo`,\n`m1`,\n`m2`,\n`std`")]
fn test_find_def_id_by_name_panic_when_no_item_with_matching_name() {
let test_src = r#"
pub extern "C" fn foo() {}
pub mod m1 {
pub fn bar() {}
}
pub mod m2 {
pub fn bar() {}
}
"#;
run_compiler(test_src, |tcx| find_def_id_by_name(tcx, "missing_name"));
}
#[test]
#[should_panic(expected = "More than one item named `some_name`")]
fn test_find_def_id_by_name_panic_when_multiple_items_with_matching_name() {
let test_src = r#"
pub mod m1 {
pub fn some_name() {}
}
pub mod m2 {
pub fn some_name() {}
}
"#;
run_compiler(test_src, |tcx| find_def_id_by_name(tcx, "some_name"));
}
#[test]
fn test_generated_bindings_fn_success() {
// This test covers only a single example of a function that should get a C++
// binding. Additional coverage of how items are formatted is provided by
// `test_format_def_...` tests.
let test_src = r#"
pub extern "C" fn public_function() {
println!("foo");
}
"#;
test_generated_bindings(test_src, |bindings| {
assert_cc_matches!(
bindings.h_body,
quote! {
extern "C" void public_function();
}
);
});
}
#[test]
fn test_generated_bindings_fn_non_pub() {
let test_src = r#"
#![allow(dead_code)]
extern "C" fn private_function() {
println!("foo");
}
"#;
test_generated_bindings(test_src, |bindings| {
// Non-public functions should not be present in the generated bindings.
assert_cc_not_matches!(bindings.h_body, quote! { private_function });
});
}
#[test]
fn test_generated_bindings_top_level_items() {
let test_src = "pub fn public_function() {}";
test_generated_bindings(test_src, |bindings| {
let expected_comment_txt =
"Automatically @generated C++ bindings for the following Rust crate:\n\
rust_out";
assert_cc_matches!(
bindings.h_body,
quote! {
__COMMENT__ #expected_comment_txt
...
__HASH_TOKEN__ pragma once
...
namespace rust_out {
...
}
}
);
})
}
#[test]
fn test_generated_bindings_unsupported_item() {
// This test verifies how `Err` from `format_def` is formatted as a C++ comment
// (in `format_crate` and `format_unsupported_def`).
// - This test covers only a single example of an unsupported item. Additional
// coverage is provided by `test_format_def_unsupported_...` tests.
// - This test somewhat arbitrarily chooses an example of an unsupported item,
// trying to pick one that 1) will never be supported (b/254104998 has some extra
// notes about APIs named after reserved C++ keywords) and 2) tests that the
// full error chain is included in the message.
let test_src = r#"
pub extern "C" fn reinterpret_cast() {}
"#;
test_generated_bindings(test_src, |bindings| {
let expected_comment_txt = "Error generating bindings for `reinterpret_cast` \
defined at <crubit_unittests.rs>:2:17: 2:53: \
Error formatting function name: \
`reinterpret_cast` is a C++ reserved keyword \
and can't be used as a C++ identifier";
assert_cc_matches!(
bindings.h_body,
quote! {
__COMMENT__ #expected_comment_txt
}
);
})
}
#[test]
fn test_format_def_fn_extern_c_no_params_no_return_type() {
let test_src = r#"
pub extern "C" fn public_function() {}
"#;
test_format_def(test_src, "public_function", |result| {
assert_cc_matches!(
result.expect("Test expects success here"),
quote! {
extern "C" void public_function();
}
);
});
}
#[test]
fn test_format_def_fn_extern_c_no_params_unit_return_type() {
// This test is very similar to the
// `test_format_def_fn_extern_c_no_params_no_return_type` above, except
// that the return type is explicitly spelled out. There is no difference in
// `ty::FnSig` so our code behaves exactly the same, but the test has been
// planned based on earlier, hir-focused approach and having this extra
// test coverage shouldn't hurt. (`hir::FnSig` and `hir::FnRetTy` _do_
// see a difference between the two tests).
let test_src = r#"
pub extern "C" fn explicit_unit_return_type() -> () {}
"#;
test_format_def(test_src, "explicit_unit_return_type", |result| {
assert_cc_matches!(
result.expect("Test expects success here"),
quote! {
extern "C" void explicit_unit_return_type();
}
);
});
}
#[test]
fn test_format_def_unsupported_fn_unsafe() {
// This tests how bindings for an `unsafe fn` are generated.
let test_src = r#"
pub unsafe extern "C" fn foo() {}
"#;
test_format_def(test_src, "foo", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Bindings for `unsafe` functions \
are not fully designed yet (b/254095482)"
);
});
}
#[test]
fn test_format_def_fn_const() {
// This tests how bindings for an `const fn` are generated.
//
// Right now the `const` qualifier is ignored, but one can imagine that in the
// (very) long-term future such functions (including their bodies) could
// be translated into C++ `consteval` functions.
let test_src = r#"
pub const fn foo(i: i32) -> i32 { i * 42 }
"#;
test_format_def(test_src, "foo", |result| {
// TODO(lukasza): Update test expectations below once `const fn` example from
// the testcase doesn't just error out (and is instead supported as
// a non-`consteval` binding).
// TODO(b/254095787): Update test expectations below once `const fn` from Rust
// is translated into a `consteval` C++ function.
let err = result.expect_err("Test expects an error here");
assert_eq!(err, "Function parameters are not supported yet",);
});
}
#[test]
fn test_format_def_fn_with_c_unwind_abi() {
// See also https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html
let test_src = r#"
#![feature(c_unwind)]
pub extern "C-unwind" fn may_throw() {}
"#;
test_format_def(test_src, "may_throw", |result| {
assert_cc_matches!(
result.expect("Test expects success here"),
quote! {
extern "C" void may_throw();
}
);
});
}
#[test]
fn test_format_def_fn_with_type_aliased_return_type() {
// Type aliases disappear at the `rustc_middle::ty::Ty` level and therefore in
// the short-term the generated bindings also ignore type aliases.
//
// TODO(b/254096006): Consider preserving `type` aliases when generating
// bindings.
let test_src = r#"
type MyTypeAlias = ();
pub extern "C" fn type_aliased_return() -> MyTypeAlias {}
"#;
test_format_def(test_src, "type_aliased_return", |result| {
assert_cc_matches!(
result.expect("Test expects success here"),
quote! {
extern "C" void type_aliased_return();
}
);
});
}
#[test]
fn test_format_def_unsupported_fn_name_is_reserved_cpp_keyword() {
let test_src = r#"
pub extern "C" fn reinterpret_cast() -> () {}
"#;
test_format_def(test_src, "reinterpret_cast", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Error formatting function name: \
`reinterpret_cast` is a C++ reserved keyword \
and can't be used as a C++ identifier"
);
});
}
#[test]
fn test_format_def_unsupported_fn_ret_type() {
let test_src = r#"
pub extern "C" fn foo() -> *const i32 { std::ptr::null() }
"#;
test_format_def(test_src, "foo", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Error formatting function return type: \
The following Rust type is not supported yet: *const i32"
);
});
}
#[test]
fn test_format_def_unsupported_fn_with_late_bound_lifetimes() {
let test_src = r#"
pub fn foo(arg: &i32) -> &i32 { arg }
// Lifetime inference translates the above into:
// pub fn foo<'a>(arg: &'a i32) -> &'a i32 { ... }
// leaving 'a lifetime late-bound (it is bound with a lifetime
// taken from each of the callsites). In other words, we can't
// just call `no_bound_vars` on this `FnSig`'s `Binder`.
"#;
test_format_def(test_src, "foo", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Generic functions (lifetime-generic or type-generic) are not supported yet"
);
});
}
#[test]
fn test_format_def_unsupported_generic_fn() {
let test_src = r#"
use std::default::Default;
use std::fmt::Display;
pub fn generic_function<T: Default + Display>() {
println!("{}", T::default());
}
"#;
test_format_def(test_src, "generic_function", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Generic functions (lifetime-generic or type-generic) are not supported yet"
);
});
}
#[test]
fn test_format_def_unsupported_fn_async() {
let test_src = r#"
pub async fn async_function() {}
"#;
test_format_def(test_src, "async_function", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Functions that require Rust thunks (e.g. non-`extern \"C\"`) \
are not supported yet (b/254097223)"
);
});
}
#[test]
fn test_format_def_unsupported_fn_non_c_abi() {
let test_src = r#"
pub fn default_rust_abi_function() {}
"#;
test_format_def(test_src, "default_rust_abi_function", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(
err,
"Functions that require Rust thunks \
(e.g. non-`extern \"C\"`) are not supported yet (b/254097223)"
);
})
}
#[test]
fn test_format_def_unsupported_fn_variadic() {
let test_src = r#"
#![feature(c_variadic)]
pub unsafe extern "C" fn variadic_function(_fmt: *const u8, ...) {}
"#;
test_format_def(test_src, "variadic_function", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(err, "C variadic functions are not supported (b/254097223)");
});
}
#[test]
fn test_format_def_unsupported_fn_params() {
let test_src = r#"
pub unsafe extern "C" fn fn_with_params(_i: i32) {}
"#;
test_format_def(test_src, "fn_with_params", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(err, "Function parameters are not supported yet");
});
}
#[test]
fn test_format_def_unsupported_hir_item_kind() {
let test_src = r#"
pub struct SomeStruct(i32);
"#;
test_format_def(test_src, "SomeStruct", |result| {
let err = result.expect_err("Test expects an error here");
assert_eq!(err, "Unsupported rustc_hir::hir::ItemKind: struct");
});
}
/// Tests invoking `format_def` on the item with the specified `name` from
/// the given Rust `source`. Returns the result of calling
/// `test_function` with `format_def`'s result as an argument.
/// (`test_function` should typically `assert!` that it got the expected
/// result from `format_def`.)
fn test_format_def<F, T>(source: &str, name: &str, test_function: F) -> T
where
F: FnOnce(Result<TokenStream, String>) -> T + Send,
T: Send,
{
run_compiler(source, |tcx| {
let def_id = find_def_id_by_name(tcx, name);
let result = format_def(tcx, def_id);
// https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations says:
// To print causes as well [...], use the alternate selector “{:#}”.
let result = result.map_err(|anyhow_err| format!("{anyhow_err:#}"));
test_function(result)
})
}
/// Finds the definition id of a Rust item with the specified `name`.
/// Panics if no such item is found, or if there is more than one match.
fn find_def_id_by_name(tcx: TyCtxt, name: &str) -> LocalDefId {
let hir_items = || tcx.hir().items().map(|item_id| tcx.hir().item(item_id));
let items_with_matching_name =
hir_items().filter(|item| item.ident.name.as_str() == name).collect_vec();
match items_with_matching_name.as_slice() {
&[] => {
let found_names = hir_items()
.map(|item| item.ident.name.as_str())
.filter(|s| !s.is_empty())
.sorted()
.dedup()
.map(|name| format!("`{name}`"))
.collect_vec();
panic!("No items named `{}`.\nInstead found:\n{}", name, found_names.join(",\n"));
}
&[item] => item.def_id.def_id,
_ => panic!("More than one item named `{name}`"),
}
}
/// Tests invoking `GeneratedBindings::generate` on the given Rust `source`.
/// Returns the result of calling `test_function` with the generated
/// bindings as an argument. (`test_function` should typically `assert!`
/// that it got the expected `GeneratedBindings`.)
fn test_generated_bindings<F, T>(source: &str, test_function: F) -> T
where
F: FnOnce(GeneratedBindings) -> T + Send,
T: Send,
{
run_compiler(source, |tcx| test_function(GeneratedBindings::generate(tcx)))
}
/// Invokes the Rust compiler on the given Rust `source` and then calls `f`
/// on the `TyCtxt` representation of the compiled `source`.
fn run_compiler<F, T>(source: impl Into<String>, f: F) -> T
where
F: for<'tcx> FnOnce(TyCtxt<'tcx>) -> T + Send,
T: Send,
{
use rustc_session::config::{CrateType, Input, Options, OutputType, OutputTypes};
const TEST_FILENAME: &str = "crubit_unittests.rs";
// Setting `output_types` that will trigger code gen - otherwise some parts of
// the analysis will be missing (e.g. `tcx.exported_symbols()`).
// The choice of `Bitcode` is somewhat arbitrary (e.g. `Assembly`,
// `Mir`, etc. would also trigger code gen).
let output_types = OutputTypes::new(&[(OutputType::Bitcode, None /* PathBuf */)]);
let opts = Options {
crate_types: vec![CrateType::Rlib], // Test inputs simulate library crates.
maybe_sysroot: Some(get_sysroot_for_testing()),
output_types,
edition: rustc_span::edition::Edition::Edition2021,
unstable_features: rustc_feature::UnstableFeatures::Allow,
lint_opts: vec![
("warnings".to_string(), rustc_lint_defs::Level::Deny),
("stable_features".to_string(), rustc_lint_defs::Level::Allow),
],
..Default::default()
};
let config = rustc_interface::interface::Config {
opts,
crate_cfg: Default::default(),
crate_check_cfg: Default::default(),
input: Input::Str {
name: rustc_span::FileName::Custom(TEST_FILENAME.to_string()),
input: source.into(),
},
input_path: None,
output_file: None,
output_dir: None,
file_loader: None,
diagnostic_output: rustc_session::DiagnosticOutput::Default,
lint_caps: Default::default(),
parse_sess_created: None,
register_lints: None,
override_queries: None,
make_codegen_backend: None,
registry: rustc_errors::registry::Registry::new(rustc_error_codes::DIAGNOSTICS),
};
rustc_interface::interface::run_compiler(config, |compiler| {
compiler.enter(|queries| {
use rustc_interface::interface::Result;
let result: Result<Result<()>> = super::enter_tcx(queries, |tcx| {
// Explicitly force full `analysis` stage to detect compilation
// errors that the earlier stages might miss. This helps ensure that the
// test inputs are valid Rust (even if `f` wouldn't
// have triggered full analysis).
tcx.analysis(())
});
// Flatten the outer and inner results into a single result. (outer result
// comes from `enter_tcx`; inner result comes from `analysis`).
//
// TODO(lukasza): Use `Result::flatten` API when it gets stabilized. See also
// https://github.com/rust-lang/rust/issues/70142
let result: Result<()> = result.and_then(|result| result);
// `analysis` might succeed even if there are some lint / warning errors.
// Detecting these requires explicitly checking `compile_status`.
let result: Result<()> = result.and_then(|()| compiler.session().compile_status());
// Run the provided callback.
let result: Result<T> = result.and_then(|()| super::enter_tcx(queries, f));
result.expect("Test inputs shouldn't cause compilation errors")
})
})
}
}