Skip to content

Commit 47693fc

Browse files
authored
Unrolled build for rust-lang#140420
Rollup merge of rust-lang#140420 - fmease:rustdoc-fix-doctest-heur, r=GuillaumeGomez rustdoc: Fix doctest heuristic for main fn wrapping Fixes rust-lang#140412 which regressed in rust-lang#140220 that I reviewed. As mentioned in rust-lang#140220 (comment), at the time I didn't have the time to re-review its latest changes and should've therefore invalided my previous "r=me" and blocked the PR on another review given the fragile nature of the doctest impl. This didn't happen which is my fault. Contains some other small changes. Diff best reviewed modulo whitespace. r? ``@GuillaumeGomez``
2 parents 3350c1e + 714ea10 commit 47693fc

12 files changed

+182
-88
lines changed

src/librustdoc/doctest/make.rs

+28-37
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
301301

302302
let filename = FileName::anon_source_code(&wrapped_source);
303303

304-
// Any errors in parsing should also appear when the doctest is compiled for real, so just
305-
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
306304
let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
307305
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
308306
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
@@ -311,7 +309,8 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
311309
info.supports_color =
312310
HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
313311
.supports_color();
314-
312+
// Any errors in parsing should also appear when the doctest is compiled for real, so just
313+
// send all the errors that the parser emits directly into a `Sink` instead of stderr.
315314
let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);
316315

317316
// FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
@@ -339,9 +338,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
339338
*prev_span_hi = hi;
340339
}
341340

342-
// Recurse through functions body. It is necessary because the doctest source code is
343-
// wrapped in a function to limit the number of AST errors. If we don't recurse into
344-
// functions, we would thing all top-level items (so basically nothing).
345341
fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) -> bool {
346342
let mut is_extern_crate = false;
347343
if !info.has_global_allocator
@@ -351,8 +347,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
351347
}
352348
match item.kind {
353349
ast::ItemKind::Fn(ref fn_item) if !info.has_main_fn => {
354-
// We only push if it's the top item because otherwise, we would duplicate
355-
// its content since the top-level item was already added.
356350
if fn_item.ident.name == sym::main {
357351
info.has_main_fn = true;
358352
}
@@ -412,44 +406,41 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
412406
let mut is_extern_crate = false;
413407
match stmt.kind {
414408
StmtKind::Item(ref item) => {
415-
is_extern_crate = check_item(&item, &mut info, crate_name);
416-
}
417-
StmtKind::Expr(ref expr) => {
418-
if matches!(expr.kind, ast::ExprKind::Err(_)) {
419-
reset_error_count(&psess);
420-
return Err(());
421-
}
422-
has_non_items = true;
409+
is_extern_crate = check_item(item, &mut info, crate_name);
423410
}
424411
// We assume that the macro calls will expand to item(s) even though they could
425-
// expand to statements and expressions. And the simple fact that we're trying
426-
// to retrieve a `main` function inside it is a terrible idea.
412+
// expand to statements and expressions.
427413
StmtKind::MacCall(ref mac_call) => {
428-
if info.has_main_fn {
429-
continue;
430-
}
431-
let mut iter = mac_call.mac.args.tokens.iter();
432-
433-
while let Some(token) = iter.next() {
434-
if let TokenTree::Token(token, _) = token
435-
&& let TokenKind::Ident(name, _) = token.kind
436-
&& name == kw::Fn
437-
&& let Some(TokenTree::Token(fn_token, _)) = iter.peek()
438-
&& let TokenKind::Ident(fn_name, _) = fn_token.kind
439-
&& fn_name == sym::main
440-
&& let Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _)) = {
441-
iter.next();
442-
iter.peek()
414+
if !info.has_main_fn {
415+
// For backward compatibility, we look for the token sequence `fn main(…)`
416+
// in the macro input (!) to crudely detect main functions "masked by a
417+
// wrapper macro". For the record, this is a horrible heuristic!
418+
// See <https://github.com/rust-lang/rust/issues/56898>.
419+
let mut iter = mac_call.mac.args.tokens.iter();
420+
while let Some(token) = iter.next() {
421+
if let TokenTree::Token(token, _) = token
422+
&& let TokenKind::Ident(kw::Fn, _) = token.kind
423+
&& let Some(TokenTree::Token(ident, _)) = iter.peek()
424+
&& let TokenKind::Ident(sym::main, _) = ident.kind
425+
&& let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, _)) = {
426+
iter.next();
427+
iter.peek()
428+
}
429+
{
430+
info.has_main_fn = true;
431+
break;
443432
}
444-
{
445-
info.has_main_fn = true;
446-
break;
447433
}
448434
}
449435
}
450-
_ => {
436+
StmtKind::Expr(ref expr) => {
437+
if matches!(expr.kind, ast::ExprKind::Err(_)) {
438+
reset_error_count(&psess);
439+
return Err(());
440+
}
451441
has_non_items = true;
452442
}
443+
StmtKind::Let(_) | StmtKind::Semi(_) | StmtKind::Empty => has_non_items = true,
453444
}
454445

455446
// Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
fn item() {}

tests/rustdoc-ui/doctest/auxiliary/macro-after-main.rs

-1
This file was deleted.

tests/rustdoc-ui/doctest/macro-after-main.rs

-16
This file was deleted.

tests/rustdoc-ui/doctest/macro-after-main.stdout

-6
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
running 4 tests
3+
test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok
4+
test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok
5+
test $DIR/main-alongside-macro-calls.rs - (line 28) ... FAILED
6+
test $DIR/main-alongside-macro-calls.rs - (line 33) ... FAILED
7+
8+
failures:
9+
10+
---- $DIR/main-alongside-macro-calls.rs - (line 28) stdout ----
11+
error: macros that expand to items must be delimited with braces or followed by a semicolon
12+
--> $DIR/main-alongside-macro-calls.rs:30:1
13+
|
14+
LL | println!();
15+
| ^^^^^^^^^^
16+
|
17+
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
18+
19+
error: macro expansion ignores `{` and any tokens following
20+
--> $SRC_DIR/std/src/macros.rs:LL:COL
21+
|
22+
::: $DIR/main-alongside-macro-calls.rs:30:1
23+
|
24+
LL | println!();
25+
| ---------- caused by the macro expansion here
26+
|
27+
= note: the usage of `print!` is likely invalid in item context
28+
29+
error: aborting due to 2 previous errors
30+
31+
Couldn't compile the test.
32+
---- $DIR/main-alongside-macro-calls.rs - (line 33) stdout ----
33+
error: macros that expand to items must be delimited with braces or followed by a semicolon
34+
--> $DIR/main-alongside-macro-calls.rs:34:1
35+
|
36+
LL | println!();
37+
| ^^^^^^^^^^
38+
|
39+
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
40+
41+
error: macro expansion ignores `{` and any tokens following
42+
--> $SRC_DIR/std/src/macros.rs:LL:COL
43+
|
44+
::: $DIR/main-alongside-macro-calls.rs:34:1
45+
|
46+
LL | println!();
47+
| ---------- caused by the macro expansion here
48+
|
49+
= note: the usage of `print!` is likely invalid in item context
50+
51+
error: aborting due to 2 previous errors
52+
53+
Couldn't compile the test.
54+
55+
failures:
56+
$DIR/main-alongside-macro-calls.rs - (line 28)
57+
$DIR/main-alongside-macro-calls.rs - (line 33)
58+
59+
test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
60+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
running 4 tests
3+
test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok
4+
test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok
5+
test $DIR/main-alongside-macro-calls.rs - (line 28) - compile fail ... ok
6+
test $DIR/main-alongside-macro-calls.rs - (line 33) - compile fail ... ok
7+
8+
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
9+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// This test ensures that if there is are any macro calls alongside a `main` function,
2+
// it will indeed consider the `main` function as the program entry point and *won't*
3+
// generate its own `main` function to wrap everything even though macro calls are
4+
// valid in statement contexts, too, and could just as well expand to statements or
5+
// expressions (we don't perform any macro expansion to find `main`, see also
6+
// <https://github.com/rust-lang/rust/issues/57415>).
7+
//
8+
// See <./main-alongside-stmts.rs> for comparison.
9+
//
10+
//@ compile-flags:--test --test-args --test-threads=1
11+
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
12+
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
13+
//@ revisions: pass fail
14+
//@[pass] check-pass
15+
//@[fail] failure-status: 101
16+
17+
// Regression test for <https://github.com/rust-lang/rust/pull/140220#issuecomment-2831872920>:
18+
19+
//! ```
20+
//! fn main() {}
21+
//! include!("./auxiliary/items.rs");
22+
//! ```
23+
//!
24+
//! ```
25+
//! include!("./auxiliary/items.rs");
26+
//! fn main() {}
27+
//! ```
28+
29+
// Regression test for <https://github.com/rust-lang/rust/issues/140412>:
30+
// We test the "same" thing twice: Once via `compile_fail` to more closely mirror the reported
31+
// regression and once without it to make sure that it leads to the expected rustc errors,
32+
// namely `println!(…)` not being valid in item contexts.
33+
34+
#![cfg_attr(pass, doc = " ```compile_fail")]
35+
#![cfg_attr(fail, doc = " ```")]
36+
//! fn main() {}
37+
//! println!();
38+
//! ```
39+
//!
40+
#![cfg_attr(pass, doc = " ```compile_fail")]
41+
#![cfg_attr(fail, doc = " ```")]
42+
//! println!();
43+
//! fn main() {}
44+
//! ```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// This test ensures that if there is are any statements alongside a `main` function,
2+
// it will not consider the `main` function as the program entry point but instead
3+
// will generate its own `main` function to wrap everything as it needs to reside in a
4+
// module where only *items* are permitted syntactically.
5+
//
6+
// See <./main-alongside-macro-calls.rs> for comparison.
7+
//
8+
// This is a regression test for:
9+
// * <https://github.com/rust-lang/rust/issues/140162>
10+
// * <https://github.com/rust-lang/rust/issues/139651>
11+
//
12+
//@ compile-flags:--test --test-args --test-threads=1
13+
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
14+
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
15+
//@ check-pass
16+
17+
//! ```
18+
//! # if cfg!(miri) { return; }
19+
//! use std::ops::Deref;
20+
//!
21+
//! fn main() {
22+
//! assert!(false);
23+
//! }
24+
//! ```
25+
//!
26+
//! ```
27+
//! let x = 2;
28+
//! assert_eq!(x, 2);
29+
//!
30+
//! fn main() {
31+
//! assert!(false);
32+
//! }
33+
//! ```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
2+
running 2 tests
3+
test $DIR/main-alongside-stmts.rs - (line 17) ... ok
4+
test $DIR/main-alongside-stmts.rs - (line 26) ... ok
5+
6+
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
7+

tests/rustdoc-ui/doctest/test-main-alongside-exprs.rs

-22
This file was deleted.

tests/rustdoc-ui/doctest/test-main-alongside-exprs.stdout

-6
This file was deleted.

0 commit comments

Comments
 (0)