Don't merge doctests with #[global_allocator]

This commit is contained in:
Guillaume Gomez 2024-06-13 16:39:56 +02:00
parent 6eabffbaec
commit b6831bbdda
2 changed files with 69 additions and 74 deletions

View File

@ -49,18 +49,24 @@ pub(crate) fn new(
has_features, has_features,
has_no_std, has_no_std,
} = partition_source(source, edition); } = partition_source(source, edition);
let mut supports_color = false;
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
// crate already is included. // crate already is included.
let Ok((has_main_fn, already_has_extern_crate, failed_ast)) = let Ok((
check_for_main_and_extern_crate( ParseSourceInfo {
has_main_fn,
found_extern_crate,
supports_color,
has_global_allocator,
..
},
failed_ast,
)) = check_for_main_and_extern_crate(
crate_name, crate_name,
source, source,
&everything_else, &everything_else,
&crates, &crates,
edition, edition,
&mut supports_color,
can_merge_doctests, can_merge_doctests,
) )
else { else {
@ -86,12 +92,12 @@ pub(crate) fn new(
maybe_crate_attrs, maybe_crate_attrs,
crates, crates,
everything_else, everything_else,
already_has_extern_crate, already_has_extern_crate: found_extern_crate,
test_id, test_id,
failed_ast: false, failed_ast: false,
// If the AST returned an error, we don't want this doctest to be merged with the // If the AST returned an error, we don't want this doctest to be merged with the
// others. Same if it contains `#[feature]` or `#[no_std]`. // others. Same if it contains `#[feature]` or `#[no_std]`.
can_be_merged: !failed_ast && !has_no_std && !has_features, can_be_merged: !failed_ast && !has_no_std && !has_features && !has_global_allocator,
} }
} }
@ -231,11 +237,8 @@ fn cancel_error_count(psess: &ParseSess) {
fn parse_source( fn parse_source(
source: String, source: String,
has_main_fn: &mut bool, info: &mut ParseSourceInfo,
found_extern_crate: &mut bool,
found_macro: &mut bool,
crate_name: &Option<&str>, crate_name: &Option<&str>,
supports_color: &mut bool,
) -> ParsingResult { ) -> ParsingResult {
use rustc_errors::emitter::{Emitter, HumanEmitter}; use rustc_errors::emitter::{Emitter, HumanEmitter};
use rustc_errors::DiagCtxt; use rustc_errors::DiagCtxt;
@ -251,7 +254,7 @@ fn parse_source(
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
false, false,
); );
*supports_color = info.supports_color =
HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone()) HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
.supports_color(); .supports_color();
@ -274,43 +277,38 @@ fn parse_source(
// Recurse through functions body. It is necessary because the doctest source code is // Recurse through functions body. It is necessary because the doctest source code is
// wrapped in a function to limit the number of AST errors. If we don't recurse into // wrapped in a function to limit the number of AST errors. If we don't recurse into
// functions, we would thing all top-level items (so basically nothing). // functions, we would thing all top-level items (so basically nothing).
fn check_item( fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) {
item: &ast::Item, if !info.has_global_allocator
has_main_fn: &mut bool, && item.attrs.iter().any(|attr| attr.name_or_empty() == sym::global_allocator)
found_extern_crate: &mut bool, {
found_macro: &mut bool, info.has_global_allocator = true;
crate_name: &Option<&str>, }
) {
match item.kind { match item.kind {
ast::ItemKind::Fn(ref fn_item) if !*has_main_fn => { ast::ItemKind::Fn(ref fn_item) if !info.has_main_fn => {
if item.ident.name == sym::main { if item.ident.name == sym::main {
*has_main_fn = true; info.has_main_fn = true;
} }
if let Some(ref body) = fn_item.body { if let Some(ref body) = fn_item.body {
for stmt in &body.stmts { for stmt in &body.stmts {
match stmt.kind { match stmt.kind {
ast::StmtKind::Item(ref item) => check_item( ast::StmtKind::Item(ref item) => check_item(item, info, crate_name),
item, ast::StmtKind::MacCall(..) => info.found_macro = true,
has_main_fn,
found_extern_crate,
found_macro,
crate_name,
),
ast::StmtKind::MacCall(..) => *found_macro = true,
_ => {} _ => {}
} }
} }
} }
} }
ast::ItemKind::ExternCrate(original) => { ast::ItemKind::ExternCrate(original) => {
if !*found_extern_crate && let Some(ref crate_name) = crate_name { if !info.found_extern_crate
*found_extern_crate = match original { && let Some(ref crate_name) = crate_name
{
info.found_extern_crate = match original {
Some(name) => name.as_str() == *crate_name, Some(name) => name.as_str() == *crate_name,
None => item.ident.as_str() == *crate_name, None => item.ident.as_str() == *crate_name,
}; };
} }
} }
ast::ItemKind::MacCall(..) => *found_macro = true, ast::ItemKind::MacCall(..) => info.found_macro = true,
_ => {} _ => {}
} }
} }
@ -318,9 +316,9 @@ fn check_item(
loop { loop {
match parser.parse_item(ForceCollect::No) { match parser.parse_item(ForceCollect::No) {
Ok(Some(item)) => { Ok(Some(item)) => {
check_item(&item, has_main_fn, found_extern_crate, found_macro, crate_name); check_item(&item, info, crate_name);
if *has_main_fn && *found_extern_crate { if info.has_main_fn && info.found_extern_crate {
break; break;
} }
} }
@ -341,30 +339,30 @@ fn check_item(
parsing_result parsing_result
} }
/// Returns `(has_main_fn, already_has_extern_crate, failed_ast)`. #[derive(Default)]
struct ParseSourceInfo {
has_main_fn: bool,
found_extern_crate: bool,
found_macro: bool,
supports_color: bool,
has_global_allocator: bool,
}
fn check_for_main_and_extern_crate( fn check_for_main_and_extern_crate(
crate_name: Option<&str>, crate_name: Option<&str>,
original_source_code: &str, original_source_code: &str,
everything_else: &str, everything_else: &str,
crates: &str, crates: &str,
edition: Edition, edition: Edition,
supports_color: &mut bool,
can_merge_doctests: bool, can_merge_doctests: bool,
) -> Result<(bool, bool, bool), FatalError> { ) -> Result<(ParseSourceInfo, bool), FatalError> {
let result = rustc_driver::catch_fatal_errors(|| { let result = rustc_driver::catch_fatal_errors(|| {
rustc_span::create_session_if_not_set_then(edition, |_| { rustc_span::create_session_if_not_set_then(edition, |_| {
let mut has_main_fn = false; let mut info =
let mut found_extern_crate = crate_name.is_none(); ParseSourceInfo { found_extern_crate: crate_name.is_none(), ..Default::default() };
let mut found_macro = false;
let mut parsing_result = parse_source( let mut parsing_result =
format!("{crates}{everything_else}"), parse_source(format!("{crates}{everything_else}"), &mut info, &crate_name);
&mut has_main_fn,
&mut found_extern_crate,
&mut found_macro,
&crate_name,
supports_color,
);
// No need to double-check this if the "merged doctests" feature isn't enabled (so // No need to double-check this if the "merged doctests" feature isn't enabled (so
// before the 2024 edition). // before the 2024 edition).
if can_merge_doctests && parsing_result != ParsingResult::Ok { if can_merge_doctests && parsing_result != ParsingResult::Ok {
@ -380,30 +378,25 @@ fn check_for_main_and_extern_crate(
// faster doctests run time. // faster doctests run time.
parsing_result = parse_source( parsing_result = parse_source(
format!("{crates}\nfn __doctest_wrap(){{{everything_else}\n}}"), format!("{crates}\nfn __doctest_wrap(){{{everything_else}\n}}"),
&mut has_main_fn, &mut info,
&mut found_extern_crate,
&mut found_macro,
&crate_name, &crate_name,
supports_color,
); );
} }
(has_main_fn, found_extern_crate, found_macro, parsing_result) (info, parsing_result)
}) })
}); });
let (mut has_main_fn, already_has_extern_crate, found_macro, parsing_result) = match result { let (mut info, parsing_result) = match result {
Err(..) | Ok((_, _, _, ParsingResult::Failed)) => return Err(FatalError), Err(..) | Ok((_, ParsingResult::Failed)) => return Err(FatalError),
Ok((has_main_fn, already_has_extern_crate, found_macro, parsing_result)) => { Ok((info, parsing_result)) => (info, parsing_result),
(has_main_fn, already_has_extern_crate, found_macro, parsing_result)
}
}; };
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
// see it. In that case, run the old text-based scan to see if they at least have a main // see it. In that case, run the old text-based scan to see if they at least have a main
// function written inside a macro invocation. See // function written inside a macro invocation. See
// https://github.com/rust-lang/rust/issues/56898 // https://github.com/rust-lang/rust/issues/56898
if found_macro if info.found_macro
&& !has_main_fn && !info.has_main_fn
&& original_source_code && original_source_code
.lines() .lines()
.map(|line| { .map(|line| {
@ -412,10 +405,10 @@ fn check_for_main_and_extern_crate(
}) })
.any(|code| code.contains("fn main")) .any(|code| code.contains("fn main"))
{ {
has_main_fn = true; info.has_main_fn = true;
} }
Ok((has_main_fn, already_has_extern_crate, parsing_result != ParsingResult::Ok)) Ok((info, parsing_result != ParsingResult::Ok))
} }
enum AttrKind { enum AttrKind {

View File

@ -45,11 +45,11 @@ pub(crate) fn add_test(
self.crate_attrs.insert(line.to_string()); self.crate_attrs.insert(line.to_string());
} }
} }
if !self.ids.is_empty() { // if !self.ids.is_empty() {
self.ids.push(','); // self.ids.push(',');
} // }
self.ids.push_str(&format!( self.ids.push_str(&format!(
"{}::TEST", "tests.push({}::TEST);\n",
generate_mergeable_doctest( generate_mergeable_doctest(
doctest, doctest,
scraped_test, scraped_test,
@ -107,9 +107,11 @@ pub(crate) fn run_merged_tests(
#[rustc_main] #[rustc_main]
#[coverage(off)] #[coverage(off)]
fn main() {{ fn main() {{
test::test_main_static_with_args( let mut tests = Vec::new();
{ids}
test::test_main(
&[{test_args}], &[{test_args}],
&mut [{ids}], tests,
None, None,
); );
}}", }}",