Skip to content

[beta] backports #140592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 3, 2025
6 changes: 6 additions & 0 deletions compiler/rustc_codegen_ssa/src/back/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2011,6 +2011,12 @@ fn add_linked_symbol_object(
file.set_mangling(object::write::Mangling::None);
}

if file.format() == object::BinaryFormat::MachO {
// Divide up the sections into sub-sections via symbols for dead code stripping.
// Without this flag, unused `#[no_mangle]` or `#[used]` cannot be discard on MachO targets.
file.set_subsections_via_symbols();
}

// ld64 requires a relocation to load undefined symbols, see below.
// Not strictly needed if linking with lld, but might as well do it there too.
let ld64_section_helper = if file.format() == object::BinaryFormat::MachO {
Expand Down
9 changes: 8 additions & 1 deletion library/core/src/fmt/rt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,15 @@ impl Argument<'_> {
/// let f = format_args!("{}", "a");
/// println!("{f}");
/// ```
///
/// This function should _not_ be const, to make sure we don't accept
/// format_args!() and panic!() with arguments in const, even when not evaluated:
///
/// ```compile_fail,E0015
/// const _: () = if false { panic!("a {}", "a") };
/// ```
#[inline]
pub const fn none() -> [Self; 0] {
pub fn none() -> [Self; 0] {
[]
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/bootstrap/src/core/build_steps/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2570,9 +2570,9 @@ fn prepare_cargo_test(
// We skip everything on Miri as then this overwrites the libdir set up
// by `Cargo::new` and that actually makes things go wrong.
if builder.kind != Kind::Miri {
let mut dylib_path = dylib_path();
dylib_path.insert(0, PathBuf::from(&*builder.sysroot_target_libdir(compiler, target)));
cargo.env(dylib_path_var(), env::join_paths(&dylib_path).unwrap());
let mut dylib_paths = builder.rustc_lib_paths(compiler);
dylib_paths.push(PathBuf::from(&builder.sysroot_target_libdir(compiler, target)));
helpers::add_dylib_path(dylib_paths, &mut cargo);
}

if builder.remote_tested(target) {
Expand Down
68 changes: 38 additions & 30 deletions src/librustdoc/doctest/make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn

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

// Any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
let sm = Arc::new(SourceMap::new(FilePathMapping::empty()));
let fallback_bundle = rustc_errors::fallback_fluent_bundle(
rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(),
Expand All @@ -311,7 +309,8 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
info.supports_color =
HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle.clone())
.supports_color();

// Any errors in parsing should also appear when the doctest is compiled for real, so just
// send all the errors that the parser emits directly into a `Sink` instead of stderr.
let emitter = HumanEmitter::new(Box::new(io::sink()), fallback_bundle);

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

// 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
// functions, we would thing all top-level items (so basically nothing).
fn check_item(item: &ast::Item, info: &mut ParseSourceInfo, crate_name: &Option<&str>) -> bool {
let mut is_extern_crate = false;
if !info.has_global_allocator
Expand All @@ -351,8 +347,6 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
}
match item.kind {
ast::ItemKind::Fn(_) if !info.has_main_fn => {
// We only push if it's the top item because otherwise, we would duplicate
// its content since the top-level item was already added.
if item.ident.name == sym::main {
info.has_main_fn = true;
}
Expand Down Expand Up @@ -411,37 +405,46 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
push_to_s(&mut info.crate_attrs, source, attr.span, &mut prev_span_hi);
}
}
let mut has_non_items = false;
for stmt in &body.stmts {
let mut is_extern_crate = false;
match stmt.kind {
StmtKind::Item(ref item) => {
is_extern_crate = check_item(&item, &mut info, crate_name);
}
StmtKind::Expr(ref expr) if matches!(expr.kind, ast::ExprKind::Err(_)) => {
reset_error_count(&psess);
return Err(());
is_extern_crate = check_item(item, &mut info, crate_name);
}
StmtKind::MacCall(ref mac_call) if !info.has_main_fn => {
let mut iter = mac_call.mac.args.tokens.iter();

while let Some(token) = iter.next() {
if let TokenTree::Token(token, _) = token
&& let TokenKind::Ident(name, _) = token.kind
&& name == kw::Fn
&& let Some(TokenTree::Token(fn_token, _)) = iter.peek()
&& let TokenKind::Ident(fn_name, _) = fn_token.kind
&& fn_name == sym::main
&& let Some(TokenTree::Delimited(_, _, Delimiter::Parenthesis, _)) = {
iter.next();
iter.peek()
// We assume that the macro calls will expand to item(s) even though they could
// expand to statements and expressions.
StmtKind::MacCall(ref mac_call) => {
if !info.has_main_fn {
// For backward compatibility, we look for the token sequence `fn main(…)`
// in the macro input (!) to crudely detect main functions "masked by a
// wrapper macro". For the record, this is a horrible heuristic!
// See <https://github.com/rust-lang/rust/issues/56898>.
let mut iter = mac_call.mac.args.tokens.iter();
while let Some(token) = iter.next() {
if let TokenTree::Token(token, _) = token
&& let TokenKind::Ident(kw::Fn, _) = token.kind
&& let Some(TokenTree::Token(ident, _)) = iter.peek()
&& let TokenKind::Ident(sym::main, _) = ident.kind
&& let Some(TokenTree::Delimited(.., Delimiter::Parenthesis, _)) = {
iter.next();
iter.peek()
}
{
info.has_main_fn = true;
break;
}
{
info.has_main_fn = true;
break;
}
}
}
_ => {}
StmtKind::Expr(ref expr) => {
if matches!(expr.kind, ast::ExprKind::Err(_)) {
reset_error_count(&psess);
return Err(());
}
has_non_items = true;
}
StmtKind::Let(_) | StmtKind::Semi(_) | StmtKind::Empty => has_non_items = true,
}

// Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
Expand All @@ -466,6 +469,11 @@ fn parse_source(source: &str, crate_name: &Option<&str>) -> Result<ParseSourceIn
push_to_s(&mut info.crates, source, span, &mut prev_span_hi);
}
}
if has_non_items {
// FIXME: if `info.has_main_fn` is `true`, emit a warning here to mention that
// this code will not be called.
info.has_main_fn = false;
}
Ok(info)
}
Err(e) => {
Expand Down
1 change: 1 addition & 0 deletions tests/rustdoc-ui/doctest/auxiliary/items.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fn item() {}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
//@ compile-flags:--test
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ failure-status: 101
//@ check-pass

/// <https://github.com/rust-lang/rust/issues/91014>
///
/// ```rust
/// struct S {}; // unexpected semicolon after struct def
/// struct S {};
///
/// fn main() {
/// assert_eq!(0, 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@

running 1 test
test $DIR/failed-doctest-extra-semicolon-on-item.rs - m (line 11) ... FAILED
test $DIR/failed-doctest-extra-semicolon-on-item.rs - m (line 11) ... ok

failures:

---- $DIR/failed-doctest-extra-semicolon-on-item.rs - m (line 11) stdout ----
error: expected item, found `;`
--> $DIR/failed-doctest-extra-semicolon-on-item.rs:12:12
|
LL | struct S {}; // unexpected semicolon after struct def
| ^
|
= help: braced struct declarations are not followed by a semicolon
help: remove this semicolon
|
LL - struct S {}; // unexpected semicolon after struct def
LL + struct S {} // unexpected semicolon after struct def
|

error: aborting due to 1 previous error

Couldn't compile the test.

failures:
$DIR/failed-doctest-extra-semicolon-on-item.rs - m (line 11)

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

60 changes: 60 additions & 0 deletions tests/rustdoc-ui/doctest/main-alongside-macro-calls.fail.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

running 4 tests
test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok
test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok
test $DIR/main-alongside-macro-calls.rs - (line 28) ... FAILED
test $DIR/main-alongside-macro-calls.rs - (line 33) ... FAILED

failures:

---- $DIR/main-alongside-macro-calls.rs - (line 28) stdout ----
error: macros that expand to items must be delimited with braces or followed by a semicolon
--> $DIR/main-alongside-macro-calls.rs:30:1
|
LL | println!();
| ^^^^^^^^^^
|
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: macro expansion ignores `{` and any tokens following
--> $SRC_DIR/std/src/macros.rs:LL:COL
|
::: $DIR/main-alongside-macro-calls.rs:30:1
|
LL | println!();
| ---------- caused by the macro expansion here
|
= note: the usage of `print!` is likely invalid in item context

error: aborting due to 2 previous errors

Couldn't compile the test.
---- $DIR/main-alongside-macro-calls.rs - (line 33) stdout ----
error: macros that expand to items must be delimited with braces or followed by a semicolon
--> $DIR/main-alongside-macro-calls.rs:34:1
|
LL | println!();
| ^^^^^^^^^^
|
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: macro expansion ignores `{` and any tokens following
--> $SRC_DIR/std/src/macros.rs:LL:COL
|
::: $DIR/main-alongside-macro-calls.rs:34:1
|
LL | println!();
| ---------- caused by the macro expansion here
|
= note: the usage of `print!` is likely invalid in item context

error: aborting due to 2 previous errors

Couldn't compile the test.

failures:
$DIR/main-alongside-macro-calls.rs - (line 28)
$DIR/main-alongside-macro-calls.rs - (line 33)

test result: FAILED. 2 passed; 2 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

running 4 tests
test $DIR/main-alongside-macro-calls.rs - (line 19) ... ok
test $DIR/main-alongside-macro-calls.rs - (line 24) ... ok
test $DIR/main-alongside-macro-calls.rs - (line 28) - compile fail ... ok
test $DIR/main-alongside-macro-calls.rs - (line 33) - compile fail ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

44 changes: 44 additions & 0 deletions tests/rustdoc-ui/doctest/main-alongside-macro-calls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// This test ensures that if there is are any macro calls alongside a `main` function,
// it will indeed consider the `main` function as the program entry point and *won't*
// generate its own `main` function to wrap everything even though macro calls are
// valid in statement contexts, too, and could just as well expand to statements or
// expressions (we don't perform any macro expansion to find `main`, see also
// <https://github.com/rust-lang/rust/issues/57415>).
//
// See <./main-alongside-stmts.rs> for comparison.
//
//@ compile-flags:--test --test-args --test-threads=1
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ revisions: pass fail
//@[pass] check-pass
//@[fail] failure-status: 101

// Regression test for <https://github.com/rust-lang/rust/pull/140220#issuecomment-2831872920>:

//! ```
//! fn main() {}
//! include!("./auxiliary/items.rs");
//! ```
//!
//! ```
//! include!("./auxiliary/items.rs");
//! fn main() {}
//! ```

// Regression test for <https://github.com/rust-lang/rust/issues/140412>:
// We test the "same" thing twice: Once via `compile_fail` to more closely mirror the reported
// regression and once without it to make sure that it leads to the expected rustc errors,
// namely `println!(…)` not being valid in item contexts.

#![cfg_attr(pass, doc = " ```compile_fail")]
#![cfg_attr(fail, doc = " ```")]
//! fn main() {}
//! println!();
//! ```
//!
#![cfg_attr(pass, doc = " ```compile_fail")]
#![cfg_attr(fail, doc = " ```")]
//! println!();
//! fn main() {}
//! ```
33 changes: 33 additions & 0 deletions tests/rustdoc-ui/doctest/main-alongside-stmts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// This test ensures that if there is are any statements alongside a `main` function,
// it will not consider the `main` function as the program entry point but instead
// will generate its own `main` function to wrap everything as it needs to reside in a
// module where only *items* are permitted syntactically.
//
// See <./main-alongside-macro-calls.rs> for comparison.
//
// This is a regression test for:
// * <https://github.com/rust-lang/rust/issues/140162>
// * <https://github.com/rust-lang/rust/issues/139651>
//
//@ compile-flags:--test --test-args --test-threads=1
//@ normalize-stdout: "tests/rustdoc-ui/doctest" -> "$$DIR"
//@ normalize-stdout: "finished in \d+\.\d+s" -> "finished in $$TIME"
//@ check-pass

//! ```
//! # if cfg!(miri) { return; }
//! use std::ops::Deref;
//!
//! fn main() {
//! assert!(false);
//! }
//! ```
//!
//! ```
//! let x = 2;
//! assert_eq!(x, 2);
//!
//! fn main() {
//! assert!(false);
//! }
//! ```
7 changes: 7 additions & 0 deletions tests/rustdoc-ui/doctest/main-alongside-stmts.stdout
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

running 2 tests
test $DIR/main-alongside-stmts.rs - (line 17) ... ok
test $DIR/main-alongside-stmts.rs - (line 26) ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME

5 changes: 5 additions & 0 deletions tests/ui/consts/const-eval/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,9 @@ const fn print() {
//~| ERROR cannot call non-const function `_print` in constant functions
}

const fn format_args() {
format_args!("{}", 0);
//~^ ERROR cannot call non-const formatting macro in constant functions
}

fn main() {}
10 changes: 9 additions & 1 deletion tests/ui/consts/const-eval/format.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ LL | println!("{:?}", 0);
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
= note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 3 previous errors
error[E0015]: cannot call non-const formatting macro in constant functions
--> $DIR/format.rs:13:5
|
LL | format_args!("{}", 0);
| ^^^^^^^^^^^^^^^^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0015`.
Loading
Loading