From dea80f716cabc76f42621a062f3ad4b2b7a0dca2 Mon Sep 17 00:00:00 2001 From: arferreira Date: Mon, 20 Apr 2026 13:14:29 -0400 Subject: [PATCH 01/39] Normalize .. and . in diagnostic file paths --- compiler/rustc_span/src/lib.rs | 39 +++++++++++++------ compiler/rustc_span/src/source_map.rs | 2 +- compiler/rustc_span/src/source_map/tests.rs | 11 ++++++ src/tools/compiletest/src/runtest.rs | 10 +++++ tests/ui/README.md | 4 ++ .../generic_arg_infer/issue-91614.stderr | 4 +- tests/ui/diagnostics/auxiliary/helper.rs | 3 ++ tests/ui/diagnostics/auxiliary/sub/mod.rs | 2 + tests/ui/diagnostics/normalize-path.rs | 9 +++++ tests/ui/diagnostics/normalize-path.stderr | 11 ++++++ 10 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 tests/ui/diagnostics/auxiliary/helper.rs create mode 100644 tests/ui/diagnostics/auxiliary/sub/mod.rs create mode 100644 tests/ui/diagnostics/normalize-path.rs create mode 100644 tests/ui/diagnostics/normalize-path.stderr diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index f6ae748560750..b23d072a9253e 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -22,6 +22,7 @@ #![feature(diagnostic_on_unknown)] #![feature(map_try_insert)] #![feature(negative_impls)] +#![feature(normalize_lexically)] #![feature(read_buf)] #![feature(rustc_attrs)] // tidy-alphabetical-end @@ -499,6 +500,15 @@ impl RealFileName { .file_name() .map_or_else(|| "".into(), |f| f.to_string_lossy()), FileNameDisplayPreference::Scope(scope) => self.path(scope).to_string_lossy(), + FileNameDisplayPreference::Diagnostics(scope) => { + let path = self.path(scope); + match path.normalize_lexically() { + Ok(normalized) => { + Cow::Owned(normalized.into_os_string().to_string_lossy().into_owned()) + } + Err(_) => path.to_string_lossy(), + } + } } } } @@ -536,15 +546,23 @@ enum FileNameDisplayPreference { Local, Short, Scope(RemapPathScopeComponents), + Diagnostics(RemapPathScopeComponents), +} + +impl<'a> FileNameDisplay<'a> { + pub fn to_string_lossy(&self) -> Cow<'a, str> { + match self.inner { + FileName::Real(inner) => inner.to_string_lossy(self.display_pref), + _ => Cow::from(self.to_string()), + } + } } impl fmt::Display for FileNameDisplay<'_> { fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { use FileName::*; match *self.inner { - Real(ref name) => { - write!(fmt, "{}", name.to_string_lossy(self.display_pref)) - } + Real(ref name) => write!(fmt, "{}", name.to_string_lossy(self.display_pref)), CfgSpec(_) => write!(fmt, ""), MacroExpansion(_) => write!(fmt, ""), Anon(_) => write!(fmt, ""), @@ -557,15 +575,6 @@ impl fmt::Display for FileNameDisplay<'_> { } } -impl<'a> FileNameDisplay<'a> { - pub fn to_string_lossy(&self) -> Cow<'a, str> { - match self.inner { - FileName::Real(inner) => inner.to_string_lossy(self.display_pref), - _ => Cow::from(self.to_string()), - } - } -} - impl FileName { pub fn is_real(&self) -> bool { use FileName::*; @@ -612,6 +621,12 @@ impl FileName { FileNameDisplay { inner: self, display_pref: FileNameDisplayPreference::Scope(scope) } } + /// Like `display`, but with `.` and `..` resolved lexically. See #51349. + #[inline] + pub fn display_normalized(&self, scope: RemapPathScopeComponents) -> FileNameDisplay<'_> { + FileNameDisplay { inner: self, display_pref: FileNameDisplayPreference::Diagnostics(scope) } + } + pub fn macro_expansion_source_code(src: &str) -> FileName { let mut hasher = StableHasher::new(); src.hash(&mut hasher); diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index 80d1bae71ae89..cf92e3386cde1 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -521,7 +521,7 @@ impl SourceMap { } pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { - filename.display(RemapPathScopeComponents::DIAGNOSTICS) + filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) } pub fn is_multiline(&self, sp: Span) -> bool { diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index 4cc243667f224..acc186a007fca 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -797,3 +797,14 @@ fn read_binary_file_handles_lying_stat() { let bin = RealFileLoader.read_binary_file(kernel_max).unwrap(); assert_eq!(&real[..], &bin[..]); } + +#[test] +fn filename_for_diagnostics_resolves_parent_dir() { + let sm = SourceMap::new(FilePathMapping::empty()); + + let with_parent = filename(&sm, "tests/sub/../helper.rs"); + assert_eq!(sm.filename_for_diagnostics(&with_parent).to_string(), path_str("tests/helper.rs")); + + let clean = filename(&sm, "tests/clean.rs"); + assert_eq!(sm.filename_for_diagnostics(&clean).to_string(), path_str("tests/clean.rs")); +} diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs index 8f45e037d0fc9..6a3372def36ac 100644 --- a/src/tools/compiletest/src/runtest.rs +++ b/src/tools/compiletest/src/runtest.rs @@ -2491,12 +2491,22 @@ impl<'test> TestCx<'test> { let parent_dir = self.testpaths.file.parent().unwrap(); normalize_path(parent_dir, "$DIR"); + // After #51349, rustc normalizes `tests/x/y/../aux/foo.rs` to + // `tests/x/aux/foo.rs`. Replace the grandparent with `$DIR/..` so + // stderrs keep the pre-normalization form. + if let Some(grandparent_dir) = parent_dir.parent() { + normalize_path(grandparent_dir, "$DIR/.."); + } + if self.props.remap_src_base { let mut remapped_parent_dir = Utf8PathBuf::from(FAKE_SRC_BASE); if self.testpaths.relative_dir != Utf8Path::new("") { remapped_parent_dir.push(&self.testpaths.relative_dir); } normalize_path(&remapped_parent_dir, "$DIR"); + if let Some(remapped_grandparent) = remapped_parent_dir.parent() { + normalize_path(remapped_grandparent, "$DIR/.."); + } } let base_dir = Utf8Path::new("/rustc/FAKE_PREFIX"); diff --git a/tests/ui/README.md b/tests/ui/README.md index a3617fb6b07c9..e91b3b4b75e80 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -446,6 +446,10 @@ Everything to do with `--diagnostic-width`. Exercises `#[diagnostic::*]` namespaced attributes. See [RFC 3368 Diagnostic attribute namespace](https://github.com/rust-lang/rfcs/blob/master/text/3368-diagnostic-attribute-namespace.md). +## `tests/ui/diagnostics/` + +Tests for diagnostic output quality, such as path normalization in error messages. + ## `tests/ui/did_you_mean/` Tests for miscellaneous suggestions. diff --git a/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr b/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr index 164bcc7111ca6..6a2952604587e 100644 --- a/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr +++ b/tests/ui/const-generics/generic_arg_infer/issue-91614.stderr @@ -5,7 +5,7 @@ LL | let y = Mask::<_, _>::splat(false); | ^ ------------ type must be known at this point | note: required by a const generic parameter in `Mask` - --> $SRC_DIR/core/src/../../portable-simd/crates/core_simd/src/masks.rs:LL:COL + --> $SRC_DIR/portable-simd/crates/core_simd/src/masks.rs:LL:COL help: consider giving `y` an explicit type, where the value of const parameter `N` is specified | LL | let y: Mask<_, N> = Mask::<_, _>::splat(false); @@ -18,7 +18,7 @@ LL | let y = Mask::<_, _>::splat(false); | ^ -------------------------- type must be known at this point | note: required by a const generic parameter in `Mask::::splat` - --> $SRC_DIR/core/src/../../portable-simd/crates/core_simd/src/masks.rs:LL:COL + --> $SRC_DIR/portable-simd/crates/core_simd/src/masks.rs:LL:COL help: consider giving `y` an explicit type, where the value of const parameter `N` is specified | LL | let y: Mask<_, N> = Mask::<_, _>::splat(false); diff --git a/tests/ui/diagnostics/auxiliary/helper.rs b/tests/ui/diagnostics/auxiliary/helper.rs new file mode 100644 index 0000000000000..83103ab7bd71f --- /dev/null +++ b/tests/ui/diagnostics/auxiliary/helper.rs @@ -0,0 +1,3 @@ +pub fn foo() -> u32 { + "not a u32" +} diff --git a/tests/ui/diagnostics/auxiliary/sub/mod.rs b/tests/ui/diagnostics/auxiliary/sub/mod.rs new file mode 100644 index 0000000000000..dd531abbce552 --- /dev/null +++ b/tests/ui/diagnostics/auxiliary/sub/mod.rs @@ -0,0 +1,2 @@ +#[path = "../helper.rs"] +mod helper; diff --git a/tests/ui/diagnostics/normalize-path.rs b/tests/ui/diagnostics/normalize-path.rs new file mode 100644 index 0000000000000..1b1fb998abf01 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path.rs @@ -0,0 +1,9 @@ +// Verify that diagnostic file paths are lexically normalized. +// Without the fix for #51349, the error location would show +// `auxiliary/sub/../helper.rs` instead of `auxiliary/helper.rs`. +#[path = "auxiliary/sub/mod.rs"] +mod sub; + +fn main() {} + +//~? ERROR mismatched types diff --git a/tests/ui/diagnostics/normalize-path.stderr b/tests/ui/diagnostics/normalize-path.stderr new file mode 100644 index 0000000000000..9eb92e54853e1 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/auxiliary/helper.rs:2:5 + | +LL | pub fn foo() -> u32 { + | --- expected `u32` because of return type +LL | "not a u32" + | ^^^^^^^^^^^ expected `u32`, found `&str` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From 0da8bd366f03535a639c195c0eba539dd53205f1 Mon Sep 17 00:00:00 2001 From: arferreira Date: Thu, 13 Aug 2026 19:41:20 -0400 Subject: [PATCH 02/39] Print unnormalized diagnostic paths under --verbose Signed-off-by: arferreira --- compiler/rustc_interface/src/interface.rs | 8 +++++++- compiler/rustc_interface/src/tests.rs | 1 + compiler/rustc_span/src/source_map.rs | 20 +++++++++++++++++-- compiler/rustc_span/src/source_map/tests.rs | 17 ++++++++++++++++ .../ui/diagnostics/normalize-path-verbose.rs | 10 ++++++++++ .../diagnostics/normalize-path-verbose.stderr | 11 ++++++++++ tests/ui/diagnostics/normalize-path.rs | 6 +++--- 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 tests/ui/diagnostics/normalize-path-verbose.rs create mode 100644 tests/ui/diagnostics/normalize-path-verbose.stderr diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs index 2737d2ca854a5..f145b1129aa60 100644 --- a/compiler/rustc_interface/src/interface.rs +++ b/compiler/rustc_interface/src/interface.rs @@ -401,7 +401,13 @@ pub fn run_compiler(config: Config, f: impl FnOnce(&Compiler) -> R + Se config.opts.edition, jobs, &config.extra_symbols, - SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }, + SourceMapInputs { + file_loader, + path_mapping, + hash_kind, + checksum_hash_kind, + verbose: config.opts.verbose, + }, |current_gcx| { // The previous `early_dcx` can't be reused here because it doesn't // impl `Send`. Creating a new one is fine. diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs index 24c7ff8484a5e..8d2e167c81238 100644 --- a/compiler/rustc_interface/src/tests.rs +++ b/compiler/rustc_interface/src/tests.rs @@ -53,6 +53,7 @@ where path_mapping: sessopts.file_path_mapping(), hash_kind, checksum_hash_kind, + verbose: sessopts.verbose, }); rustc_span::create_session_globals_then(DEFAULT_EDITION, &[], sm_inputs, || { diff --git a/compiler/rustc_span/src/source_map.rs b/compiler/rustc_span/src/source_map.rs index cf92e3386cde1..88fc0922791ad 100644 --- a/compiler/rustc_span/src/source_map.rs +++ b/compiler/rustc_span/src/source_map.rs @@ -192,6 +192,7 @@ pub struct SourceMapInputs { pub path_mapping: FilePathMapping, pub hash_kind: SourceFileHashAlgorithm, pub checksum_hash_kind: Option, + pub verbose: bool, } pub struct SourceMap { @@ -213,6 +214,10 @@ pub struct SourceMap { /// /// If this is equal to `hash_kind` then the checksum won't be computed twice. checksum_hash_kind: Option, + + /// Whether `--verbose` was passed. Diagnostics then print paths without + /// lexical normalization. + verbose: bool, } impl std::fmt::Debug for SourceMap { @@ -224,6 +229,7 @@ impl std::fmt::Debug for SourceMap { working_dir, hash_kind, checksum_hash_kind, + verbose, } = self; f.debug_struct("SourceMap") @@ -233,6 +239,7 @@ impl std::fmt::Debug for SourceMap { .field("working_dir", working_dir) .field("hash_kind", hash_kind) .field("checksum_hash_kind", checksum_hash_kind) + .field("verbose", verbose) .finish() } } @@ -244,11 +251,12 @@ impl SourceMap { path_mapping, hash_kind: SourceFileHashAlgorithm::Md5, checksum_hash_kind: None, + verbose: false, }) } pub fn with_inputs( - SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind }: SourceMapInputs, + SourceMapInputs { file_loader, path_mapping, hash_kind, checksum_hash_kind, verbose }: SourceMapInputs, ) -> SourceMap { let cwd = file_loader .current_directory() @@ -262,6 +270,7 @@ impl SourceMap { path_mapping, hash_kind, checksum_hash_kind, + verbose, } } @@ -520,8 +529,15 @@ impl SourceMap { self.lookup_char_pos(sp.lo()).file.name.clone() } + /// Paths are normalized lexically, which can name the wrong file if a + /// component is a symlink. `--verbose` skips normalization and prints the + /// path as given. pub fn filename_for_diagnostics<'a>(&self, filename: &'a FileName) -> FileNameDisplay<'a> { - filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) + if self.verbose { + filename.display(RemapPathScopeComponents::DIAGNOSTICS) + } else { + filename.display_normalized(RemapPathScopeComponents::DIAGNOSTICS) + } } pub fn is_multiline(&self, sp: Span) -> bool { diff --git a/compiler/rustc_span/src/source_map/tests.rs b/compiler/rustc_span/src/source_map/tests.rs index acc186a007fca..b97c96787a96b 100644 --- a/compiler/rustc_span/src/source_map/tests.rs +++ b/compiler/rustc_span/src/source_map/tests.rs @@ -808,3 +808,20 @@ fn filename_for_diagnostics_resolves_parent_dir() { let clean = filename(&sm, "tests/clean.rs"); assert_eq!(sm.filename_for_diagnostics(&clean).to_string(), path_str("tests/clean.rs")); } + +#[test] +fn filename_for_diagnostics_verbose_keeps_parent_dir() { + let sm = SourceMap::with_inputs(SourceMapInputs { + file_loader: Box::new(RealFileLoader), + path_mapping: FilePathMapping::empty(), + hash_kind: SourceFileHashAlgorithm::Md5, + checksum_hash_kind: None, + verbose: true, + }); + + let with_parent = filename(&sm, "tests/sub/../helper.rs"); + assert_eq!( + sm.filename_for_diagnostics(&with_parent).to_string(), + path_str("tests/sub/../helper.rs"), + ); +} diff --git a/tests/ui/diagnostics/normalize-path-verbose.rs b/tests/ui/diagnostics/normalize-path-verbose.rs new file mode 100644 index 0000000000000..8955ce10da98b --- /dev/null +++ b/tests/ui/diagnostics/normalize-path-verbose.rs @@ -0,0 +1,10 @@ +//@ compile-flags: --verbose + +// Check that `--verbose` prints diagnostic paths as given, without lexical +// normalization. See #51349. +#[path = "auxiliary/sub/mod.rs"] +mod sub; + +fn main() {} + +//~? ERROR mismatched types diff --git a/tests/ui/diagnostics/normalize-path-verbose.stderr b/tests/ui/diagnostics/normalize-path-verbose.stderr new file mode 100644 index 0000000000000..ef1c9416f46c1 --- /dev/null +++ b/tests/ui/diagnostics/normalize-path-verbose.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/auxiliary/sub/../helper.rs:2:5 + | +LL | pub fn foo() -> u32 { + | --- expected `u32` because of return type +LL | "not a u32" + | ^^^^^^^^^^^ expected `u32`, found `&str` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/diagnostics/normalize-path.rs b/tests/ui/diagnostics/normalize-path.rs index 1b1fb998abf01..bc041c790e529 100644 --- a/tests/ui/diagnostics/normalize-path.rs +++ b/tests/ui/diagnostics/normalize-path.rs @@ -1,6 +1,6 @@ -// Verify that diagnostic file paths are lexically normalized. -// Without the fix for #51349, the error location would show -// `auxiliary/sub/../helper.rs` instead of `auxiliary/helper.rs`. +// Check that diagnostic file paths are lexically normalized: +// the error below points at `auxiliary/helper.rs`, not `auxiliary/sub/../helper.rs`. +// See #51349. #[path = "auxiliary/sub/mod.rs"] mod sub; From 07683989587570cff17c939062afb606ac289ddb Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 18:07:41 +0700 Subject: [PATCH 03/39] Fix async `#[track_caller]` feature gate tests Previously, these tests were testing the wrong feature gate, and had unnecessary type errors. --- .../track-caller/async-block.afn.stderr | 12 ++-- .../track-caller/async-block.cls.stderr | 42 +++++++++++++ .../track-caller/async-block.nofeat.stderr | 12 ++-- .../async-await/track-caller/async-block.rs | 27 ++++---- .../async-closure-gate.afn.stderr | 61 +++++-------------- .../async-closure-gate.cls.stderr | 24 ++++++++ .../async-closure-gate.nofeat.stderr | 61 +++++-------------- .../track-caller/async-closure-gate.rs | 54 ++++++++-------- 8 files changed, 150 insertions(+), 143 deletions(-) create mode 100644 tests/ui/async-await/track-caller/async-block.cls.stderr create mode 100644 tests/ui/async-await/track-caller/async-closure-gate.cls.stderr diff --git a/tests/ui/async-await/track-caller/async-block.afn.stderr b/tests/ui/async-await/track-caller/async-block.afn.stderr index b6a7481a4d119..89ebe2df60609 100644 --- a/tests/ui/async-await/track-caller/async-block.afn.stderr +++ b/tests/ui/async-await/track-caller/async-block.afn.stderr @@ -1,7 +1,7 @@ error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:8:13 + --> $DIR/async-block.rs:11:13 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -9,9 +9,9 @@ LL | let _ = #[track_caller] async { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:15:13 + --> $DIR/async-block.rs:19:13 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -19,9 +19,9 @@ LL | let _ = #[track_caller] async { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:23:17 + --> $DIR/async-block.rs:28:17 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information diff --git a/tests/ui/async-await/track-caller/async-block.cls.stderr b/tests/ui/async-await/track-caller/async-block.cls.stderr new file mode 100644 index 0000000000000..0e9dbc4afa05f --- /dev/null +++ b/tests/ui/async-await/track-caller/async-block.cls.stderr @@ -0,0 +1,42 @@ +error: `#[track_caller]` on async functions is a no-op + --> $DIR/async-block.rs:16:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | +LL | / async fn foo() { +LL | | let _ = #[track_caller] +LL | | +LL | | async {}; +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #110011 for more information + = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +note: the lint level is defined here + --> $DIR/async-block.rs:6:9 + | +LL | #![deny(ungated_async_fn_track_caller)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: `#[track_caller]` on async functions is a no-op + --> $DIR/async-block.rs:24:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | +LL | / async fn foo2() { +LL | | let _ = async { +LL | | let _ = #[track_caller] +... | +LL | | }; +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #110011 for more information + = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 2 previous errors + diff --git a/tests/ui/async-await/track-caller/async-block.nofeat.stderr b/tests/ui/async-await/track-caller/async-block.nofeat.stderr index b6a7481a4d119..89ebe2df60609 100644 --- a/tests/ui/async-await/track-caller/async-block.nofeat.stderr +++ b/tests/ui/async-await/track-caller/async-block.nofeat.stderr @@ -1,7 +1,7 @@ error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:8:13 + --> $DIR/async-block.rs:11:13 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -9,9 +9,9 @@ LL | let _ = #[track_caller] async { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:15:13 + --> $DIR/async-block.rs:19:13 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -19,9 +19,9 @@ LL | let _ = #[track_caller] async { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-block.rs:23:17 + --> $DIR/async-block.rs:28:17 | -LL | let _ = #[track_caller] async { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information diff --git a/tests/ui/async-await/track-caller/async-block.rs b/tests/ui/async-await/track-caller/async-block.rs index 900d5ef25504d..f56921c017d12 100644 --- a/tests/ui/async-await/track-caller/async-block.rs +++ b/tests/ui/async-await/track-caller/async-block.rs @@ -1,27 +1,32 @@ //@ edition:2021 -//@ revisions: afn nofeat +//@ revisions: afn cls afn_cls nofeat +//@[afn_cls] check-pass #![feature(stmt_expr_attributes)] -#![cfg_attr(afn, feature(async_fn_track_caller))] +#![deny(ungated_async_fn_track_caller)] +#![cfg_attr(any(afn, afn_cls), feature(async_fn_track_caller))] +#![cfg_attr(any(cls, afn_cls), feature(closure_track_caller))] fn main() { - let _ = #[track_caller] async { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + async {}; } #[track_caller] +//[cls]~^ ERROR `#[track_caller]` on async functions is a no-op async fn foo() { - let _ = #[track_caller] async { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + async {}; } #[track_caller] +//[cls]~^ ERROR `#[track_caller]` on async functions is a no-op async fn foo2() { let _ = async { - let _ = #[track_caller] async { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + async {}; }; } diff --git a/tests/ui/async-await/track-caller/async-closure-gate.afn.stderr b/tests/ui/async-await/track-caller/async-closure-gate.afn.stderr index 6887a904211ec..fb774290bde4e 100644 --- a/tests/ui/async-await/track-caller/async-closure-gate.afn.stderr +++ b/tests/ui/async-await/track-caller/async-closure-gate.afn.stderr @@ -1,7 +1,7 @@ error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:8:13 + --> $DIR/async-closure-gate.rs:11:13 | -LL | let _ = #[track_caller] async || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -9,9 +9,9 @@ LL | let _ = #[track_caller] async || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:15:13 + --> $DIR/async-closure-gate.rs:19:13 | -LL | let _ = #[track_caller] async || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -19,9 +19,9 @@ LL | let _ = #[track_caller] async || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:21:13 + --> $DIR/async-closure-gate.rs:25:13 | -LL | let _ = #[track_caller] || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -29,9 +29,9 @@ LL | let _ = #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:29:17 + --> $DIR/async-closure-gate.rs:32:17 | -LL | let _ = #[track_caller] || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -39,9 +39,9 @@ LL | let _ = #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:37:9 + --> $DIR/async-closure-gate.rs:40:9 | -LL | #[track_caller] || { +LL | #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -49,48 +49,15 @@ LL | #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:47:13 + --> $DIR/async-closure-gate.rs:49:13 | -LL | #[track_caller] || { +LL | #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0308]: mismatched types - --> $DIR/async-closure-gate.rs:27:5 - | -LL | fn foo3() { - | - help: try adding a return type: `-> impl Future` -LL | / async { -LL | | -LL | | let _ = #[track_caller] || { -... | -LL | | } - | |_____^ expected `()`, found `async` block - | - = note: expected unit type `()` - found `async` block `{async block@$DIR/async-closure-gate.rs:27:5: 27:10}` - -error[E0308]: mismatched types - --> $DIR/async-closure-gate.rs:44:5 - | -LL | fn foo5() { - | - help: try adding a return type: `-> impl Future` -LL | / async { -LL | | -LL | | let _ = || { -LL | | #[track_caller] || { -... | -LL | | }; -LL | | } - | |_____^ expected `()`, found `async` block - | - = note: expected unit type `()` - found `async` block `{async block@$DIR/async-closure-gate.rs:44:5: 44:10}` - -error: aborting due to 8 previous errors +error: aborting due to 6 previous errors -Some errors have detailed explanations: E0308, E0658. -For more information about an error, try `rustc --explain E0308`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/async-await/track-caller/async-closure-gate.cls.stderr b/tests/ui/async-await/track-caller/async-closure-gate.cls.stderr new file mode 100644 index 0000000000000..5fe7a2fb7d36a --- /dev/null +++ b/tests/ui/async-await/track-caller/async-closure-gate.cls.stderr @@ -0,0 +1,24 @@ +error: `#[track_caller]` on async functions is a no-op + --> $DIR/async-closure-gate.rs:16:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | +LL | / async fn foo() { +LL | | let _ = #[track_caller] +LL | | +LL | | async || {}; +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #110011 for more information + = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date +note: the lint level is defined here + --> $DIR/async-closure-gate.rs:6:9 + | +LL | #![deny(ungated_async_fn_track_caller)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/async-await/track-caller/async-closure-gate.nofeat.stderr b/tests/ui/async-await/track-caller/async-closure-gate.nofeat.stderr index 6887a904211ec..fb774290bde4e 100644 --- a/tests/ui/async-await/track-caller/async-closure-gate.nofeat.stderr +++ b/tests/ui/async-await/track-caller/async-closure-gate.nofeat.stderr @@ -1,7 +1,7 @@ error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:8:13 + --> $DIR/async-closure-gate.rs:11:13 | -LL | let _ = #[track_caller] async || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -9,9 +9,9 @@ LL | let _ = #[track_caller] async || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:15:13 + --> $DIR/async-closure-gate.rs:19:13 | -LL | let _ = #[track_caller] async || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -19,9 +19,9 @@ LL | let _ = #[track_caller] async || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:21:13 + --> $DIR/async-closure-gate.rs:25:13 | -LL | let _ = #[track_caller] || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -29,9 +29,9 @@ LL | let _ = #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:29:17 + --> $DIR/async-closure-gate.rs:32:17 | -LL | let _ = #[track_caller] || { +LL | let _ = #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -39,9 +39,9 @@ LL | let _ = #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:37:9 + --> $DIR/async-closure-gate.rs:40:9 | -LL | #[track_caller] || { +LL | #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information @@ -49,48 +49,15 @@ LL | #[track_caller] || { = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date error[E0658]: `#[track_caller]` on closures is currently unstable - --> $DIR/async-closure-gate.rs:47:13 + --> $DIR/async-closure-gate.rs:49:13 | -LL | #[track_caller] || { +LL | #[track_caller] | ^^^^^^^^^^^^^^^ | = note: see issue #87417 for more information = help: add `#![feature(closure_track_caller)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -error[E0308]: mismatched types - --> $DIR/async-closure-gate.rs:27:5 - | -LL | fn foo3() { - | - help: try adding a return type: `-> impl Future` -LL | / async { -LL | | -LL | | let _ = #[track_caller] || { -... | -LL | | } - | |_____^ expected `()`, found `async` block - | - = note: expected unit type `()` - found `async` block `{async block@$DIR/async-closure-gate.rs:27:5: 27:10}` - -error[E0308]: mismatched types - --> $DIR/async-closure-gate.rs:44:5 - | -LL | fn foo5() { - | - help: try adding a return type: `-> impl Future` -LL | / async { -LL | | -LL | | let _ = || { -LL | | #[track_caller] || { -... | -LL | | }; -LL | | } - | |_____^ expected `()`, found `async` block - | - = note: expected unit type `()` - found `async` block `{async block@$DIR/async-closure-gate.rs:44:5: 44:10}` - -error: aborting due to 8 previous errors +error: aborting due to 6 previous errors -Some errors have detailed explanations: E0308, E0658. -For more information about an error, try `rustc --explain E0308`. +For more information about this error, try `rustc --explain E0658`. diff --git a/tests/ui/async-await/track-caller/async-closure-gate.rs b/tests/ui/async-await/track-caller/async-closure-gate.rs index e72ce2afa45fd..13d3a5787ca9d 100644 --- a/tests/ui/async-await/track-caller/async-closure-gate.rs +++ b/tests/ui/async-await/track-caller/async-closure-gate.rs @@ -1,52 +1,54 @@ //@ edition:2021 -//@ revisions: afn nofeat +//@ revisions: afn cls afn_cls nofeat +//@[afn_cls] check-pass #![feature(stmt_expr_attributes)] -#![cfg_attr(afn, feature(async_fn_track_caller))] +#![deny(ungated_async_fn_track_caller)] +#![cfg_attr(any(afn, afn_cls), feature(async_fn_track_caller))] +#![cfg_attr(any(cls, afn_cls), feature(closure_track_caller))] fn main() { - let _ = #[track_caller] async || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + async || {}; } #[track_caller] +//[cls]~^ ERROR `#[track_caller]` on async functions is a no-op async fn foo() { - let _ = #[track_caller] async || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + async || {}; } async fn foo2() { - let _ = #[track_caller] || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + || {}; } fn foo3() { - async { - //~^ ERROR mismatched types - let _ = #[track_caller] || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; - } + let _ = async { + let _ = #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + || {}; + }; } async fn foo4() { let _ = || { - #[track_caller] || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + || {}; }; } fn foo5() { - async { - //~^ ERROR mismatched types + let _ = async { let _ = || { - #[track_caller] || { - //~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] - }; + #[track_caller] + //[nofeat,afn]~^ ERROR `#[track_caller]` on closures is currently unstable [E0658] + || {}; }; - } + }; } From 88fb17e4a4a6e768f2476bcf8f2682915d07e8bc Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 18:17:31 +0700 Subject: [PATCH 04/39] Add a test revision to panic-track-caller.rs --- .../panic-track-caller.cls.stderr | 6 +- .../panic-track-caller.nofeat.stderr | 6 +- .../track-caller/panic-track-caller.rs | 55 ++++++++++--------- 3 files changed, 36 insertions(+), 31 deletions(-) diff --git a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr index 464cbfba2acfe..762e4add96e41 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr @@ -3,7 +3,7 @@ warning: `#[track_caller]` on async functions is a no-op | LL | #[track_caller] | ^^^^^^^^^^^^^^^ -... +LL | LL | / async fn bar_track_caller() { LL | | panic!() LL | | } @@ -15,11 +15,11 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:67:5 + --> $DIR/panic-track-caller.rs:66:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ -... +LL | LL | / async fn bar_assoc() { LL | | panic!(); LL | | } diff --git a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr index 464cbfba2acfe..762e4add96e41 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -3,7 +3,7 @@ warning: `#[track_caller]` on async functions is a no-op | LL | #[track_caller] | ^^^^^^^^^^^^^^^ -... +LL | LL | / async fn bar_track_caller() { LL | | panic!() LL | | } @@ -15,11 +15,11 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:67:5 + --> $DIR/panic-track-caller.rs:66:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ -... +LL | LL | / async fn bar_assoc() { LL | | panic!(); LL | | } diff --git a/tests/ui/async-await/track-caller/panic-track-caller.rs b/tests/ui/async-await/track-caller/panic-track-caller.rs index bd12bf11d6c84..7e6a913fa78c7 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.rs +++ b/tests/ui/async-await/track-caller/panic-track-caller.rs @@ -1,11 +1,11 @@ //@ run-pass //@ edition:2021 -//@ revisions: afn cls nofeat +//@ revisions: afn cls afn_cls nofeat //@ needs-unwind // gate-test-async_fn_track_caller #![feature(stmt_expr_attributes)] -#![cfg_attr(afn, feature(async_fn_track_caller))] -#![cfg_attr(cls, feature(closure_track_caller))] +#![cfg_attr(any(afn, afn_cls), feature(async_fn_track_caller))] +#![cfg_attr(any(cls, afn_cls), feature(closure_track_caller))] #![allow(unused)] use std::future::Future; @@ -51,8 +51,7 @@ async fn foo() { } #[track_caller] -//[cls]~^ WARN `#[track_caller]` on async functions is a no-op -//[nofeat]~^^ WARN `#[track_caller]` on async functions is a no-op +//[cls,nofeat]~^ WARN `#[track_caller]` on async functions is a no-op async fn bar_track_caller() { panic!() } @@ -65,8 +64,7 @@ struct Foo; impl Foo { #[track_caller] - //[cls]~^ WARN `#[track_caller]` on async functions is a no-op - //[nofeat]~^^ WARN `#[track_caller]` on async functions is a no-op + //[cls,nofeat]~^ WARN `#[track_caller]` on async functions is a no-op async fn bar_assoc() { panic!(); } @@ -76,21 +74,23 @@ async fn foo_assoc() { Foo::bar_assoc().await } -// Since compilation is expected to fail for this fn when using -// `nofeat`, we test that separately in `async-closure-gate.rs` -#[cfg(cls)] +// Since compilation is expected to fail for this fn when `closure_track_caller` +// is disabled, we test that separately in `async-closure-gate.rs` +#[cfg(any(cls, afn_cls))] async fn foo_closure() { - let c = #[track_caller] async || { + let c = #[track_caller] + async || { panic!(); }; c().await } -// Since compilation is expected to fail for this fn when using -// `nofeat`, we test that separately in `async-block.rs` -#[cfg(cls)] +// Since compilation is expected to fail for this fn when `closure_track_caller` +// is disabled, we test that separately in `async-closure-gate.rs` +#[cfg(any(cls, afn_cls))] async fn foo_block() { - let a = #[track_caller] async { + let a = #[track_caller] + async { panic!(); }; a.await @@ -113,22 +113,27 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { } fn main() { - assert_eq!(panicked_at(|| block_on(foo())), 46 -); + assert_eq!(panicked_at(|| block_on(foo())), 46); - #[cfg(afn)] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 61); + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 57); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 56); - #[cfg(afn)] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 76); + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 71); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 69); + // FIXME(closure_track_caller): if closure_track_caller is enabled, but + // async_fn_track_caller is disabled, then #[track_caller] on async closures + // silently do nothing. Either it should function, or we should emit a warning. + // See #161961 #[cfg(cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 84); + assert_eq!(panicked_at(|| block_on(foo_closure())), 83); + #[cfg(afn_cls)] + assert_eq!(panicked_at(|| block_on(foo_closure())), 85); - #[cfg(cls)] + #[cfg(any(cls, afn_cls))] assert_eq!(panicked_at(|| block_on(foo_block())), 96); } From 95ded567ed9e32a769339ee92b1576dcd532836e Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 18:27:40 +0700 Subject: [PATCH 05/39] In panic-track-caller.rs, split call and await into different lines. This is done so that we can test whether the location used is at the function call site or the await site. --- .../panic-track-caller.cls.stderr | 4 +-- .../panic-track-caller.nofeat.stderr | 4 +-- .../track-caller/panic-track-caller.rs | 35 +++++++++++-------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr index 762e4add96e41..2721918972b2a 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:53:1 + --> $DIR/panic-track-caller.rs:54:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:66:5 + --> $DIR/panic-track-caller.rs:68:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr index 762e4add96e41..2721918972b2a 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:53:1 + --> $DIR/panic-track-caller.rs:54:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:66:5 + --> $DIR/panic-track-caller.rs:68:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.rs b/tests/ui/async-await/track-caller/panic-track-caller.rs index 7e6a913fa78c7..15fff0d281694 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.rs +++ b/tests/ui/async-await/track-caller/panic-track-caller.rs @@ -47,7 +47,8 @@ async fn bar() { } async fn foo() { - bar().await + let future = bar(); + future.await; } #[track_caller] @@ -57,7 +58,8 @@ async fn bar_track_caller() { } async fn foo_track_caller() { - bar_track_caller().await + let future = bar_track_caller(); + future.await; } struct Foo; @@ -71,29 +73,31 @@ impl Foo { } async fn foo_assoc() { - Foo::bar_assoc().await + let future = Foo::bar_assoc(); + future.await; } // Since compilation is expected to fail for this fn when `closure_track_caller` // is disabled, we test that separately in `async-closure-gate.rs` #[cfg(any(cls, afn_cls))] async fn foo_closure() { - let c = #[track_caller] + let closure = #[track_caller] async || { panic!(); }; - c().await + let future = closure(); + future.await; } // Since compilation is expected to fail for this fn when `closure_track_caller` // is disabled, we test that separately in `async-closure-gate.rs` #[cfg(any(cls, afn_cls))] async fn foo_block() { - let a = #[track_caller] + let future = #[track_caller] async { panic!(); }; - a.await + future.await; } fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { @@ -112,28 +116,31 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { x } +// FIXME(async_fn_track_caller): Currently, #[track_caller] on an async function +// uses the location where the future is awaited. +// The correct behavior as per T-lang is to use the location where the function is called. fn main() { assert_eq!(panicked_at(|| block_on(foo())), 46); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 62); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 56); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 57); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 77); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 69); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 71); // FIXME(closure_track_caller): if closure_track_caller is enabled, but // async_fn_track_caller is disabled, then #[track_caller] on async closures // silently do nothing. Either it should function, or we should emit a warning. // See #161961 #[cfg(cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 83); + assert_eq!(panicked_at(|| block_on(foo_closure())), 86); #[cfg(afn_cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 85); + assert_eq!(panicked_at(|| block_on(foo_closure())), 89); #[cfg(any(cls, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_block())), 96); + assert_eq!(panicked_at(|| block_on(foo_block())), 100); } From 40b84d357bbe33b41ca8fc5150769fea9868092d Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 18:33:49 +0700 Subject: [PATCH 06/39] Add manual polling test to panic-track-caller.rs --- .../panic-track-caller.cls.stderr | 17 ++++++++++++++- .../panic-track-caller.nofeat.stderr | 17 ++++++++++++++- .../track-caller/panic-track-caller.rs | 21 ++++++++++++++++++- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr index 2721918972b2a..7b116387169c2 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr @@ -29,5 +29,20 @@ LL | | } = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -warning: 2 warnings emitted +warning: `#[track_caller]` on async functions is a no-op + --> $DIR/panic-track-caller.rs:103:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | +LL | / async fn bar_manual_poll() { +LL | | panic!(); +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #110011 for more information + = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +warning: 3 warnings emitted diff --git a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr index 2721918972b2a..7b116387169c2 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -29,5 +29,20 @@ LL | | } = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date -warning: 2 warnings emitted +warning: `#[track_caller]` on async functions is a no-op + --> $DIR/panic-track-caller.rs:103:1 + | +LL | #[track_caller] + | ^^^^^^^^^^^^^^^ +LL | +LL | / async fn bar_manual_poll() { +LL | | panic!(); +LL | | } + | |_- this function will not propagate the caller location + | + = note: see issue #110011 for more information + = help: add `#![feature(async_fn_track_caller)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +warning: 3 warnings emitted diff --git a/tests/ui/async-await/track-caller/panic-track-caller.rs b/tests/ui/async-await/track-caller/panic-track-caller.rs index 15fff0d281694..41835977f8d6c 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.rs +++ b/tests/ui/async-await/track-caller/panic-track-caller.rs @@ -100,6 +100,20 @@ async fn foo_block() { future.await; } +#[track_caller] +//[cls,nofeat]~^ WARN `#[track_caller]` on async functions is a no-op +async fn bar_manual_poll() { + panic!(); +} + +fn foo_manual_poll() { + let future = bar_manual_poll(); + let future = std::pin::pin!(future); + let mut cx = std::task::Context::from_waker(std::task::Waker::noop()); + let res = future.poll(&mut cx); + assert_eq!(res, std::task::Poll::Ready(())); +} + fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { let loc = Arc::new(Mutex::new(None)); @@ -117,7 +131,7 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { } // FIXME(async_fn_track_caller): Currently, #[track_caller] on an async function -// uses the location where the future is awaited. +// uses the location where the future is awaited or polled. // The correct behavior as per T-lang is to use the location where the function is called. fn main() { assert_eq!(panicked_at(|| block_on(foo())), 46); @@ -143,4 +157,9 @@ fn main() { #[cfg(any(cls, afn_cls))] assert_eq!(panicked_at(|| block_on(foo_block())), 100); + + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| foo_manual_poll()), 113); + #[cfg(any(cls, nofeat))] + assert_eq!(panicked_at(|| foo_manual_poll()), 106); } From 785e7096639aafe2658a1805bb6023a86b9079fa Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 18:55:00 +0700 Subject: [PATCH 07/39] Also test async track_caller in miri --- .../tests/pass/async-panic-track-caller.rs | 168 ++++++++++++++++++ .../panic-track-caller.cls.stderr | 6 +- .../panic-track-caller.nofeat.stderr | 6 +- .../track-caller/panic-track-caller.rs | 23 +-- 4 files changed, 187 insertions(+), 16 deletions(-) create mode 100644 src/tools/miri/tests/pass/async-panic-track-caller.rs diff --git a/src/tools/miri/tests/pass/async-panic-track-caller.rs b/src/tools/miri/tests/pass/async-panic-track-caller.rs new file mode 100644 index 0000000000000..8cb43f8fef9ce --- /dev/null +++ b/src/tools/miri/tests/pass/async-panic-track-caller.rs @@ -0,0 +1,168 @@ +// This test is duplicated (with changes) at +// tests/ui/async-await/track-caller/panic-track-caller.rs + +//@ edition:2021 +//@ revisions: afn cls afn_cls nofeat +// +// +// Padding comment so that the line numbers are the same as panic-track-caller.rs +#![feature(stmt_expr_attributes)] +#![cfg_attr(any(afn, afn_cls), feature(async_fn_track_caller))] +#![cfg_attr(any(cls, afn_cls), feature(closure_track_caller))] +#![allow(unused)] + +use std::future::Future; +use std::panic; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Wake}; +use std::thread::{self, Thread}; + +/// A waker that wakes up the current thread when called. +struct ThreadWaker(Thread); + +impl Wake for ThreadWaker { + fn wake(self: Arc) { + self.0.unpark(); + } +} + +/// Run a future to completion on the current thread. +fn block_on(fut: impl Future) -> T { + // Pin the future so it can be polled. + let mut fut = Box::pin(fut); + + // Create a new context to be passed to the future. + let t = thread::current(); + let waker = Arc::new(ThreadWaker(t)).into(); + let mut cx = Context::from_waker(&waker); + + // Run the future to completion. + loop { + match fut.as_mut().poll(&mut cx) { + Poll::Ready(res) => return res, + Poll::Pending => thread::park(), + } + } +} + +async fn bar() { + panic!() +} + +async fn foo() { + let future = bar(); + future.await; +} + +#[cfg_attr(any(cls, nofeat), expect(ungated_async_fn_track_caller))] +#[track_caller] +async fn bar_track_caller() { + panic!() +} + +async fn foo_track_caller() { + let future = bar_track_caller(); + future.await; +} + +struct Foo; + +impl Foo { + #[cfg_attr(any(cls, nofeat), expect(ungated_async_fn_track_caller))] + #[track_caller] + async fn bar_assoc() { + panic!(); + } +} + +async fn foo_assoc() { + let future = Foo::bar_assoc(); + future.await; +} + +// Since compilation is expected to fail for this fn when `closure_track_caller` +// is disabled, we test that separately in `async-closure-gate.rs` +#[cfg(any(cls, afn_cls))] +async fn foo_closure() { + let closure = #[track_caller] + async || { + panic!(); + }; + let future = closure(); + future.await; +} + +// Since compilation is expected to fail for this fn when `closure_track_caller` +// is disabled, we test that separately in `async-closure-gate.rs` +#[cfg(any(cls, afn_cls))] +async fn foo_block() { + let future = #[track_caller] + async { + panic!(); + }; + future.await; +} + +#[cfg_attr(any(cls, nofeat), expect(ungated_async_fn_track_caller))] +#[track_caller] +async fn bar_manual_poll() { + panic!(); +} + +fn foo_manual_poll() { + let future = bar_manual_poll(); + let future = std::pin::pin!(future); + let mut cx = std::task::Context::from_waker(std::task::Waker::noop()); + let res = future.poll(&mut cx); + assert_eq!(res, std::task::Poll::Ready(())); +} + +fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { + let loc = Arc::new(Mutex::new(None)); + + let hook = panic::take_hook(); + { + let loc = loc.clone(); + panic::set_hook(Box::new(move |info| { + *loc.lock().unwrap() = info.location().map(|loc| loc.line()) + })); + } + panic::catch_unwind(f).unwrap_err(); + panic::set_hook(hook); + let x = loc.lock().unwrap().unwrap(); + x +} + +// FIXME(async_fn_track_caller): Currently, #[track_caller] on an async function +// uses the location where the future is awaited or polled. +// The correct behavior as per T-lang is to use the location where the function is called. +fn main() { + assert_eq!(panicked_at(|| block_on(foo())), 49); + + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 65); + #[cfg(any(cls, nofeat))] + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); + + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| block_on(foo_assoc())), 80); + #[cfg(any(cls, nofeat))] + assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); + + // FIXME(closure_track_caller): if closure_track_caller is enabled, but + // async_fn_track_caller is disabled, then #[track_caller] on async closures + // silently do nothing. Either it should function, or we should emit a warning. + // See #161961 + #[cfg(cls)] + assert_eq!(panicked_at(|| block_on(foo_closure())), 89); + #[cfg(afn_cls)] + assert_eq!(panicked_at(|| block_on(foo_closure())), 92); + + #[cfg(any(cls, afn_cls))] + assert_eq!(panicked_at(|| block_on(foo_block())), 103); + + #[cfg(any(afn, afn_cls))] + assert_eq!(panicked_at(|| foo_manual_poll()), 116); + #[cfg(any(cls, nofeat))] + assert_eq!(panicked_at(|| foo_manual_poll()), 109); +} diff --git a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr index 7b116387169c2..7890592095b15 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:54:1 + --> $DIR/panic-track-caller.rs:57:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:68:5 + --> $DIR/panic-track-caller.rs:71:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | | } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:103:1 + --> $DIR/panic-track-caller.rs:106:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr index 7b116387169c2..7890592095b15 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:54:1 + --> $DIR/panic-track-caller.rs:57:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:68:5 + --> $DIR/panic-track-caller.rs:71:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | | } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:103:1 + --> $DIR/panic-track-caller.rs:106:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.rs b/tests/ui/async-await/track-caller/panic-track-caller.rs index 41835977f8d6c..d0e09a396217f 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.rs +++ b/tests/ui/async-await/track-caller/panic-track-caller.rs @@ -1,3 +1,6 @@ +// This test is duplicated (with changes) at +// src/tools/miri/tests/pass/async-panic-track-caller.rs + //@ run-pass //@ edition:2021 //@ revisions: afn cls afn_cls nofeat @@ -134,32 +137,32 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { // uses the location where the future is awaited or polled. // The correct behavior as per T-lang is to use the location where the function is called. fn main() { - assert_eq!(panicked_at(|| block_on(foo())), 46); + assert_eq!(panicked_at(|| block_on(foo())), 49); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 62); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 65); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 57); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 77); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 80); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 71); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); // FIXME(closure_track_caller): if closure_track_caller is enabled, but // async_fn_track_caller is disabled, then #[track_caller] on async closures // silently do nothing. Either it should function, or we should emit a warning. // See #161961 #[cfg(cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 86); - #[cfg(afn_cls)] assert_eq!(panicked_at(|| block_on(foo_closure())), 89); + #[cfg(afn_cls)] + assert_eq!(panicked_at(|| block_on(foo_closure())), 92); #[cfg(any(cls, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_block())), 100); + assert_eq!(panicked_at(|| block_on(foo_block())), 103); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| foo_manual_poll()), 113); + assert_eq!(panicked_at(|| foo_manual_poll()), 116); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| foo_manual_poll()), 106); + assert_eq!(panicked_at(|| foo_manual_poll()), 109); } From e9bdbfd470c0d0272ff060c1949296f17809378d Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas Chirananthavat)" Date: Sat, 29 Aug 2026 21:52:23 +0700 Subject: [PATCH 08/39] Exclude gcc from panic-track-caller test --- .../tests/pass/async-panic-track-caller.rs | 22 ++++++++++--------- .../panic-track-caller.cls.stderr | 6 ++--- .../panic-track-caller.nofeat.stderr | 6 ++--- .../track-caller/panic-track-caller.rs | 22 ++++++++++--------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/tools/miri/tests/pass/async-panic-track-caller.rs b/src/tools/miri/tests/pass/async-panic-track-caller.rs index 8cb43f8fef9ce..46ab0984dbdec 100644 --- a/src/tools/miri/tests/pass/async-panic-track-caller.rs +++ b/src/tools/miri/tests/pass/async-panic-track-caller.rs @@ -5,6 +5,8 @@ //@ revisions: afn cls afn_cls nofeat // // +// +// // Padding comment so that the line numbers are the same as panic-track-caller.rs #![feature(stmt_expr_attributes)] #![cfg_attr(any(afn, afn_cls), feature(async_fn_track_caller))] @@ -137,32 +139,32 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { // uses the location where the future is awaited or polled. // The correct behavior as per T-lang is to use the location where the function is called. fn main() { - assert_eq!(panicked_at(|| block_on(foo())), 49); + assert_eq!(panicked_at(|| block_on(foo())), 51); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 65); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 67); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 62); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 80); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 82); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 76); // FIXME(closure_track_caller): if closure_track_caller is enabled, but // async_fn_track_caller is disabled, then #[track_caller] on async closures // silently do nothing. Either it should function, or we should emit a warning. // See #161961 #[cfg(cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 89); + assert_eq!(panicked_at(|| block_on(foo_closure())), 91); #[cfg(afn_cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 92); + assert_eq!(panicked_at(|| block_on(foo_closure())), 94); #[cfg(any(cls, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_block())), 103); + assert_eq!(panicked_at(|| block_on(foo_block())), 105); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| foo_manual_poll()), 116); + assert_eq!(panicked_at(|| foo_manual_poll()), 118); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| foo_manual_poll()), 109); + assert_eq!(panicked_at(|| foo_manual_poll()), 111); } diff --git a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr index 7890592095b15..5611e53f50a40 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.cls.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:57:1 + --> $DIR/panic-track-caller.rs:59:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:71:5 + --> $DIR/panic-track-caller.rs:73:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | | } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:106:1 + --> $DIR/panic-track-caller.rs:108:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr index 7890592095b15..5611e53f50a40 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr +++ b/tests/ui/async-await/track-caller/panic-track-caller.nofeat.stderr @@ -1,5 +1,5 @@ warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:57:1 + --> $DIR/panic-track-caller.rs:59:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -15,7 +15,7 @@ LL | | } = note: `#[warn(ungated_async_fn_track_caller)]` on by default warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:71:5 + --> $DIR/panic-track-caller.rs:73:5 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | | } = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date warning: `#[track_caller]` on async functions is a no-op - --> $DIR/panic-track-caller.rs:106:1 + --> $DIR/panic-track-caller.rs:108:1 | LL | #[track_caller] | ^^^^^^^^^^^^^^^ diff --git a/tests/ui/async-await/track-caller/panic-track-caller.rs b/tests/ui/async-await/track-caller/panic-track-caller.rs index d0e09a396217f..294150500170d 100644 --- a/tests/ui/async-await/track-caller/panic-track-caller.rs +++ b/tests/ui/async-await/track-caller/panic-track-caller.rs @@ -1,6 +1,8 @@ // This test is duplicated (with changes) at // src/tools/miri/tests/pass/async-panic-track-caller.rs +// FIXME: catch_unwind is broken in gcc. Will be fixed in the next rustc_codegen_gcc sync. +//@ ignore-backends: gcc //@ run-pass //@ edition:2021 //@ revisions: afn cls afn_cls nofeat @@ -137,32 +139,32 @@ fn panicked_at(f: impl FnOnce() + panic::UnwindSafe) -> u32 { // uses the location where the future is awaited or polled. // The correct behavior as per T-lang is to use the location where the function is called. fn main() { - assert_eq!(panicked_at(|| block_on(foo())), 49); + assert_eq!(panicked_at(|| block_on(foo())), 51); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 65); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 67); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_track_caller())), 60); + assert_eq!(panicked_at(|| block_on(foo_track_caller())), 62); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 80); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 82); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| block_on(foo_assoc())), 74); + assert_eq!(panicked_at(|| block_on(foo_assoc())), 76); // FIXME(closure_track_caller): if closure_track_caller is enabled, but // async_fn_track_caller is disabled, then #[track_caller] on async closures // silently do nothing. Either it should function, or we should emit a warning. // See #161961 #[cfg(cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 89); + assert_eq!(panicked_at(|| block_on(foo_closure())), 91); #[cfg(afn_cls)] - assert_eq!(panicked_at(|| block_on(foo_closure())), 92); + assert_eq!(panicked_at(|| block_on(foo_closure())), 94); #[cfg(any(cls, afn_cls))] - assert_eq!(panicked_at(|| block_on(foo_block())), 103); + assert_eq!(panicked_at(|| block_on(foo_block())), 105); #[cfg(any(afn, afn_cls))] - assert_eq!(panicked_at(|| foo_manual_poll()), 116); + assert_eq!(panicked_at(|| foo_manual_poll()), 118); #[cfg(any(cls, nofeat))] - assert_eq!(panicked_at(|| foo_manual_poll()), 109); + assert_eq!(panicked_at(|| foo_manual_poll()), 111); } From d00696d3ecbef19d9680514032231380ff396193 Mon Sep 17 00:00:00 2001 From: "Tim (Theemathas) Chirananthavat" Date: Sun, 30 Aug 2026 13:26:19 +0700 Subject: [PATCH 09/39] Add run-native to async-panic-track-caller.rs Co-authored-by: Ralf Jung --- src/tools/miri/tests/pass/async-panic-track-caller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/pass/async-panic-track-caller.rs b/src/tools/miri/tests/pass/async-panic-track-caller.rs index 46ab0984dbdec..062e07cffc8b9 100644 --- a/src/tools/miri/tests/pass/async-panic-track-caller.rs +++ b/src/tools/miri/tests/pass/async-panic-track-caller.rs @@ -3,7 +3,7 @@ //@ edition:2021 //@ revisions: afn cls afn_cls nofeat -// +//@ run-native // // // From 1c4440205607cd18ccfb30abe065bf0f62b5dd18 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 31 Aug 2026 14:46:11 +1000 Subject: [PATCH 10/39] Preliminary cleanup in coverage codegen - Rename `generate_*` functions to `emit_*`, since they emit LLVM globals - Consistently use qualified paths in `counter_for_term` - Remove an unnecessary Clone from `llvm_cov::Regions` --- .../src/coverageinfo/llvm_cov.rs | 2 +- .../src/coverageinfo/mapgen.rs | 14 ++++++------- .../src/coverageinfo/mapgen/covfun.rs | 20 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs index a58202834cfa5..93d0b09578315 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/llvm_cov.rs @@ -64,7 +64,7 @@ pub(crate) fn write_filenames_to_buffer(filenames: &[impl AsRef]) -> Vec, pub(crate) expansion_regions: Vec, diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index 18818fd1a56c0..e9543ebeecf99 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -74,7 +74,7 @@ pub(crate) fn finalize(cx: &mut CodegenCx<'_, '_>) { unused::prepare_covfun_records_for_unused_functions(cx, &mut covfun_records); } - // If there are no covfun records for this CGU, don't generate a covmap record. + // If there are no covfun records for this CGU, don't emit a covmap record. // Emitting a covmap record without any covfun records causes `llvm-cov` to // fail when generating coverage reports, and if there are no covfun records // then the covmap record isn't useful anyway. @@ -89,13 +89,13 @@ pub(crate) fn finalize(cx: &mut CodegenCx<'_, '_>) { GlobalFileTable::build(tcx, covfun_records.iter().flat_map(|c| c.all_source_files())); for covfun in &covfun_records { - covfun::generate_covfun_record(cx, &global_file_table, covfun) + covfun::emit_covfun_record(cx, &global_file_table, covfun); } - // Generate the coverage map header, which contains the filenames used by + // Emit the coverage map header, which contains the filenames used by // this CGU's coverage mappings, and store it in a well-known global. // (This is skipped if we returned early due to having no covfun records.) - generate_covmap_record(cx, covmap_version, &global_file_table.filenames_buffer); + emit_covmap_record(cx, covmap_version, &global_file_table.filenames_buffer); } /// Maps "global" (per-CGU) file ID numbers to their underlying source file paths. @@ -218,10 +218,10 @@ impl VirtualFileMapping { } } -/// Generates the contents of the covmap record for this CGU, which mostly -/// consists of a header and a list of filenames. The record is then stored +/// Generates and emits the covmap record for this CGU, which mostly +/// consists of a header and a list of filenames. The record is emitted /// as a global variable in the `__llvm_covmap` section. -fn generate_covmap_record<'ll>( +fn emit_covmap_record<'ll>( cx: &mut CodegenCx<'ll, '_>, version: CovmapVersion, filenames_buffer: &[u8], diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs index 28985448fd1c1..b83570911c3a0 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs @@ -77,15 +77,15 @@ pub(crate) fn prepare_covfun_record<'tcx>( Some(covfun) } -pub(crate) fn counter_for_term(term: CovTerm) -> ffi::Counter { - use ffi::Counter; +fn counter_for_term(term: CovTerm) -> ffi::Counter { match term { - CovTerm::Zero => Counter::ZERO, - CovTerm::Counter(id) => { - Counter { kind: ffi::CounterKind::CounterValueReference, id: CounterId::as_u32(id) } - } + CovTerm::Zero => ffi::Counter::ZERO, + CovTerm::Counter(id) => ffi::Counter { + kind: ffi::CounterKind::CounterValueReference, + id: CounterId::as_u32(id), + }, CovTerm::Expression(id) => { - Counter { kind: ffi::CounterKind::Expression, id: ExpressionId::as_u32(id) } + ffi::Counter { kind: ffi::CounterKind::Expression, id: ExpressionId::as_u32(id) } } } } @@ -174,10 +174,10 @@ fn fill_region_tables<'tcx>( } } -/// Generates the contents of the covfun record for this function, which -/// contains the function's coverage mapping data. The record is then stored +/// Generates and emits the covfun record for this function, which +/// contains the function's coverage mapping data. The record is emitted /// as a global variable in the `__llvm_covfun` section. -pub(crate) fn generate_covfun_record<'tcx>( +pub(crate) fn emit_covfun_record<'tcx>( cx: &mut CodegenCx<'_, 'tcx>, global_file_table: &GlobalFileTable, covfun: &CovfunRecord<'tcx>, From be8a828aa9b2532c8d726adcafdab72831db9dcf Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 29 Aug 2026 17:15:24 +1000 Subject: [PATCH 11/39] Resolve spans to file-coordinates in a separate step One of the key tasks in coverage codegen is to take the source-code spans that were recorded during MIR instrumentation, and resolve them to physical coordinates in their respective files. In rare cases this resolution can fail, which leads to the awkward possibility that a function might lose _all_ of its mappings for a particular file/expansion. If that happens, we need to avoid emitting a covfun file section containing no regions, because doing so would trigger errors in LLVM. The existing code does handle this edge case, but in a way that won't generalise to multiple files/expansions. Having an explicit intermediate resolution step will make it easier to add support for expansion regions in the future. --- .../src/coverageinfo/mapgen.rs | 30 --- .../src/coverageinfo/mapgen/covfun.rs | 175 ++++++++++++------ 2 files changed, 118 insertions(+), 87 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs index e9543ebeecf99..3dac4b3e0167f 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs @@ -1,11 +1,9 @@ use std::assert_matches; -use std::sync::Arc; use itertools::Itertools; use rustc_abi::Align; use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, ConstCodegenMethods}; use rustc_data_structures::fx::FxIndexMap; -use rustc_index::IndexVec; use rustc_middle::ty::TyCtxt; use rustc_span::{FileName, RemapPathScopeComponents, SourceFile, StableSourceFileId}; use tracing::debug; @@ -190,34 +188,6 @@ rustc_index::newtype_index! { struct LocalFileId {} } -/// Holds a mapping from "local" (per-function) file IDs to their corresponding -/// source files. -#[derive(Debug, Default)] -struct VirtualFileMapping { - local_file_table: IndexVec>, -} - -impl VirtualFileMapping { - fn push_file(&mut self, source_file: &Arc) -> LocalFileId { - self.local_file_table.push(Arc::clone(source_file)) - } - - /// Resolves all of the filenames in this local file mapping to a list of - /// global file IDs in its CGU, for inclusion in this function's - /// `__llvm_covfun` record. - /// - /// The global file IDs are returned as `u32` to make FFI easier. - fn resolve_all(&self, global_file_table: &GlobalFileTable) -> Option> { - self.local_file_table - .iter() - .map(|file| try { - let id = global_file_table.get_existing_id(file)?; - GlobalFileId::as_u32(id) - }) - .collect::>>() - } -} - /// Generates and emits the covmap record for this CGU, which mostly /// consists of a header and a list of filenames. The record is emitted /// as a global variable in the `__llvm_covmap` section. diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs index b83570911c3a0..d01ed8765302e 100644 --- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs +++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen/covfun.rs @@ -5,13 +5,15 @@ //! [^win]: On Windows the section name is `.lcovfun`. use std::ffi::CString; +use std::iter; use std::sync::Arc; use rustc_abi::Align; use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods as _, ConstCodegenMethods}; +use rustc_index::IndexVec; use rustc_middle::mir::coverage::{ - BasicCoverageBlock, CounterId, CovTerm, CoverageCodegenInfo, CoverageMirInfo, Expression, - ExpressionId, Mapping, MappingKind, Op, + BasicCoverageBlock, CounterId, CovTerm, CoverageCodegenInfo, Expression, ExpressionId, Mapping, + MappingKind, Op, }; use rustc_middle::ty::{Instance, TyCtxt}; use rustc_span::{SourceFile, Span}; @@ -19,7 +21,7 @@ use rustc_target::spec::HasTargetSpec; use tracing::debug; use crate::common::CodegenCx; -use crate::coverageinfo::mapgen::{GlobalFileTable, VirtualFileMapping, spans}; +use crate::coverageinfo::mapgen::{GlobalFileTable, LocalFileId, spans}; use crate::coverageinfo::{ffi, llvm_cov}; use crate::llvm; @@ -34,16 +36,15 @@ pub(crate) struct CovfunRecord<'tcx> { source_hash: u64, is_used: bool, - virtual_file_mapping: VirtualFileMapping, expressions: Vec, - regions: llvm_cov::Regions, + mappings: ResolvedMappings, } impl<'tcx> CovfunRecord<'tcx> { /// Iterator that yields all source files referred to by this function's /// coverage mappings. Used to build the global file table for the CGU. pub(crate) fn all_source_files(&self) -> impl Iterator { - self.virtual_file_mapping.local_file_table.iter().map(Arc::as_ref) + self.mappings.all_source_files() } } @@ -56,24 +57,17 @@ pub(crate) fn prepare_covfun_record<'tcx>( let cg_info = tcx.coverage_codegen_info(instance.def)?; let expressions = prepare_expressions(cg_info); + let mappings = prepare_resolved_mappings(tcx, cg_info, is_used, &mir_info.mappings)?; - let mut covfun = CovfunRecord { + let covfun = CovfunRecord { _instance: instance, mangled_function_name: tcx.symbol_name(instance).name, source_hash: if is_used { mir_info.function_source_hash } else { 0 }, is_used, - virtual_file_mapping: VirtualFileMapping::default(), expressions, - regions: llvm_cov::Regions::default(), + mappings, }; - fill_region_tables(tcx, mir_info, cg_info, &mut covfun); - - if covfun.regions.has_no_regions() { - debug!(?covfun, "function has no mappings to embed; skipping"); - return None; - } - Some(covfun) } @@ -110,16 +104,60 @@ fn prepare_expressions(cg_info: &CoverageCodegenInfo) -> Vec>() } -/// Populates the mapping region tables in the current function's covfun record. -fn fill_region_tables<'tcx>( +/// Intermediate representation of coverage mappings, after all mapping spans +/// have been resolved to file coordinates (or discarded), but before producing +/// a final [`llvm_cov::Regions`]. +/// +/// Having a separate resolution step makes it easier to handle edge cases +/// where a function (or someday an expansion) manages to lose all of its spans, +/// without accidentally emitting invalid covfun records containing empty files. +#[derive(Debug)] +struct ResolvedMappings { + /// Source file for all of the [`spans::Coords`] in these mappings. + source_file: Arc, + + code_mappings: Vec, + branch_mappings: Vec, +} + +impl ResolvedMappings { + fn ensure_nonempty(self) -> Option { + let ResolvedMappings { source_file: _, code_mappings, branch_mappings } = &self; + if code_mappings.is_empty() && branch_mappings.is_empty() { None } else { Some(self) } + } + + fn all_source_files(&self) -> impl Iterator { + // FIXME(Zalathar): When expansion regions are supported, this also needs to yield + // any source files used by descendant expansions. + let ResolvedMappings { source_file, code_mappings: _, branch_mappings: _ } = self; + iter::once(source_file.as_ref()) + } +} + +/// Resolved from [`MappingKind::Code`], and the precursor to [`ffi::CodeRegion`]. +#[derive(Debug)] +struct CodeMapping { + coords: spans::Coords, + counter: ffi::Counter, +} + +/// Resolved from [`MappingKind::Branch`], and the precursor to [`ffi::BranchRegion`]. +#[derive(Debug)] +struct BranchMapping { + coords: spans::Coords, + true_counter: ffi::Counter, + false_counter: ffi::Counter, +} + +fn prepare_resolved_mappings<'tcx>( tcx: TyCtxt<'tcx>, - mir_info: &'tcx CoverageMirInfo, cg_info: &'tcx CoverageCodegenInfo, - covfun: &mut CovfunRecord<'tcx>, -) { + is_used: bool, + mappings: &[Mapping], +) -> Option { // If this function is unused, replace all counters with zero. let counter_for_bcb = |bcb: BasicCoverageBlock| -> ffi::Counter { - let term = if covfun.is_used { + let term = if is_used { cg_info.term_for_bcb[bcb].expect("every BCB in a mapping was given a term") } else { CovTerm::Zero @@ -130,14 +168,9 @@ fn fill_region_tables<'tcx>( // Currently a function's mappings must all be in the same file, so use the // first mapping's span to determine the file. let source_map = tcx.sess.source_map(); - let Some(first_span) = (try { mir_info.mappings.first()?.span }) else { - debug_assert!(false, "function has no mappings: {covfun:?}"); - return; - }; + let first_span = mappings.first()?.span; let source_file = source_map.lookup_source_file(first_span.lo()); - let local_file_id = covfun.virtual_file_mapping.push_file(&source_file); - // In rare cases, _all_ of a function's spans are discarded, and coverage // codegen needs to handle that gracefully to avoid #133606. // It's hard for tests to trigger this organically, so instead we set @@ -147,30 +180,57 @@ fn fill_region_tables<'tcx>( if discard_all { None } else { spans::make_coords(source_map, &source_file, span) } }; + let mut code_mappings = vec![]; + let mut branch_mappings = vec![]; + + for &Mapping { ref kind, span } in mappings { + let Some(coords) = make_coords(span) else { continue }; + match *kind { + MappingKind::Code { bcb } => { + code_mappings.push(CodeMapping { coords, counter: counter_for_bcb(bcb) }) + } + MappingKind::Branch { true_bcb, false_bcb } => branch_mappings.push(BranchMapping { + coords, + true_counter: counter_for_bcb(true_bcb), + false_counter: counter_for_bcb(false_bcb), + }), + } + } + + ResolvedMappings { source_file, code_mappings, branch_mappings }.ensure_nonempty() +} + +/// Populates the mapping region tables for the current function's covfun record. +fn fill_region_tables( + global_file_table: &GlobalFileTable, + mappings: &ResolvedMappings, + virtual_file_mapping: &mut IndexVec, + regions: &mut llvm_cov::Regions, +) { + let ResolvedMappings { source_file, code_mappings, branch_mappings } = mappings; + let Some(global_file_id) = global_file_table.get_existing_id(source_file) else { + debug_assert!(false, "couldn't find an existing global-file-id for {source_file:?}"); + return; + }; + let llvm_cov::Regions { code_regions, expansion_regions: _, // FIXME(Zalathar): Fill out support for expansion regions branch_regions, - } = &mut covfun.regions; + } = regions; - // For each counter/region pair in this function+file, convert it to a - // form suitable for FFI. - for &Mapping { ref kind, span } in &mir_info.mappings { - let Some(coords) = make_coords(span) else { continue }; + // The global file IDs are stored as `u32` to make FFI easier. + // FIXME(Zalathar): Consider giving `newtype_index!` a safe transmute to `&[u32]`. + let local_file_id = virtual_file_mapping.push(global_file_id.as_u32()); + + for &CodeMapping { coords, counter } in code_mappings { let cov_span = coords.make_coverage_span(local_file_id); + code_regions.push(ffi::CodeRegion { cov_span, counter }); + } - match *kind { - MappingKind::Code { bcb } => { - code_regions.push(ffi::CodeRegion { cov_span, counter: counter_for_bcb(bcb) }); - } - MappingKind::Branch { true_bcb, false_bcb } => { - branch_regions.push(ffi::BranchRegion { - cov_span, - true_counter: counter_for_bcb(true_bcb), - false_counter: counter_for_bcb(false_bcb), - }); - } - } + for &BranchMapping { coords, true_counter, false_counter } in branch_mappings { + let cov_span = coords.make_coverage_span(local_file_id); + branch_regions.push(ffi::BranchRegion { cov_span, true_counter, false_counter }); } } @@ -187,24 +247,25 @@ pub(crate) fn emit_covfun_record<'tcx>( mangled_function_name, source_hash, is_used, - ref virtual_file_mapping, ref expressions, - ref regions, + ref mappings, } = covfun; - let Some(local_file_table) = virtual_file_mapping.resolve_all(global_file_table) else { - debug_assert!( - false, - "all local files should be present in the global file table: \ - global_file_table = {global_file_table:?}, \ - virtual_file_mapping = {virtual_file_mapping:?}" - ); + let mut regions = llvm_cov::Regions::default(); + let mut virtual_file_mapping = IndexVec::new(); + fill_region_tables(global_file_table, mappings, &mut virtual_file_mapping, &mut regions); + + if regions.has_no_regions() { + debug_assert!(false, "mappings should have produced at least one region: {mappings:#?}"); return; - }; + } // Encode the function's coverage mappings into a buffer. - let coverage_mapping_buffer = - llvm_cov::write_function_mappings_to_buffer(&local_file_table, expressions, regions); + let coverage_mapping_buffer = llvm_cov::write_function_mappings_to_buffer( + &virtual_file_mapping.raw, + expressions, + ®ions, + ); // A covfun record consists of four target-endian integers, followed by the // encoded mapping data in bytes. Note that the length field is 32 bits. From 16b206a7ae3a672df2441fe5657252e2484d8911 Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Sun, 23 Aug 2026 14:32:16 +0600 Subject: [PATCH 12/39] suggest calling a fn item used as the iterator of a `for` loop `suggest_fn_call` only fired when the failing obligation came from `ObligationCauseCode::FunctionArg`, so a fn item or closure used as the iterator of a `for` loop got no structured suggestion to call it. the iterator of a `for` loop is passed to `IntoIterator::into_iter`, so the failing `Iterator` goal is a derived obligation and the cause span carries the loop desugaring, which makes `can_be_used_for_suggestions` return false. carry the `HirId` of the iterator expression in `ObligationCauseCode::ForLoopIterator` and gate the suggestion on the span of that expression instead. https://github.com/rust-lang/rust/issues/161564 --- compiler/rustc_hir_typeck/src/expr.rs | 2 +- compiler/rustc_middle/src/traits/mod.rs | 2 +- .../src/error_reporting/traits/suggestions.rs | 27 ++++++-- ...est-calling-fn-in-for-loop-issue-161564.rs | 37 ++++++++++ ...calling-fn-in-for-loop-issue-161564.stderr | 68 +++++++++++++++++++ 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.rs create mode 100644 tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 6ebf382083f25..8e3be44caf959 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -555,7 +555,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { LangItem::IntoIterIntoIter | LangItem::IteratorNext if expr.span.is_desugaring(DesugaringKind::ForLoop) => { - Some(ObligationCauseCode::ForLoopIterator) + Some(ObligationCauseCode::ForLoopIterator(arg.hir_id)) } LangItem::TryTraitFromOutput if expr.span.is_desugaring(DesugaringKind::TryBlock) => diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index 9736aabf29009..4dfc8d7c9705d 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -374,7 +374,7 @@ pub enum ObligationCauseCode<'tcx> { AwaitableExpr(HirId), - ForLoopIterator, + ForLoopIterator(HirId), QuestionMark, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs index 7921250a85a9e..a7f05d756af0f 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -1119,12 +1119,29 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { .collect::>() .join(", "); - if let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code() - && obligation.cause.span.can_be_used_for_suggestions() - { + let callee_hir_id = match obligation.cause.code() { + ObligationCauseCode::FunctionArg { arg_hir_id, .. } + if obligation.cause.span.can_be_used_for_suggestions() => + { + Some(*arg_hir_id) + } + // The iterator of a `for` loop is passed to `IntoIterator::into_iter`, so the failing + // `Iterator` goal is a derived obligation and `cause.span` carries the loop's + // desugaring context. The expression is still the user's, which its own span attests. + code => match code.peel_derives() { + ObligationCauseCode::ForLoopIterator(iter_hir_id) + if self.tcx.hir_span(*iter_hir_id).can_be_used_for_suggestions() => + { + Some(*iter_hir_id) + } + _ => None, + }, + }; + + if let Some(callee_hir_id) = callee_hir_id { let span = obligation.cause.span; - let arg_expr = match self.tcx.hir_node(*arg_hir_id) { + let arg_expr = match self.tcx.hir_node(callee_hir_id) { hir::Node::Expr(expr) => Some(expr), _ => None, }; @@ -3779,7 +3796,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { | ObligationCauseCode::ReturnValue(_) | ObligationCauseCode::BlockTailExpression(..) | ObligationCauseCode::AwaitableExpr(_) - | ObligationCauseCode::ForLoopIterator + | ObligationCauseCode::ForLoopIterator(_) | ObligationCauseCode::QuestionMark | ObligationCauseCode::CheckAssociatedTypeBounds { .. } | ObligationCauseCode::LetElse diff --git a/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.rs b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.rs new file mode 100644 index 0000000000000..5d50346b5b6fe --- /dev/null +++ b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.rs @@ -0,0 +1,37 @@ +//@ edition: 2021 + +//! Check that a fn item used as the iterator of a `for` loop is suggested to be called, the way +//! it already is when it is passed as a function argument. + +struct S; + +trait T { + fn assoc_in_trait() -> std::vec::IntoIter; +} + +impl T for S { + fn assoc_in_trait() -> std::vec::IntoIter { + vec![1u8].into_iter() + } +} + +impl S { + fn inherent_assoc() -> impl Iterator { + [1u8].into_iter() + } +} + +fn free_fn() -> impl Iterator { + [1u8].into_iter() +} + +fn main() { + for _ in S::inherent_assoc {} //~ ERROR [E0277] + for _ in ::assoc_in_trait {} //~ ERROR [E0277] + for _ in free_fn {} //~ ERROR [E0277] + + let closure = || vec![1u8].into_iter(); + for _ in closure {} //~ ERROR [E0277] + + for _ in || vec![1u8].into_iter() {} //~ ERROR [E0277] +} diff --git a/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr new file mode 100644 index 0000000000000..e2edaebb65c9b --- /dev/null +++ b/tests/ui/suggestions/suggest-calling-fn-in-for-loop-issue-161564.stderr @@ -0,0 +1,68 @@ +error[E0277]: `fn() -> impl Iterator {S::inherent_assoc}` is not an iterator + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:29:14 + | +LL | for _ in S::inherent_assoc {} + | ^^^^^^^^^^^^^^^^^ `fn() -> impl Iterator {S::inherent_assoc}` is not an iterator + | + = help: the trait `Iterator` is not implemented for fn item `fn() -> impl Iterator {S::inherent_assoc}` + = note: required for `fn() -> impl Iterator {S::inherent_assoc}` to implement `IntoIterator` +help: use parentheses to call this associated function + | +LL | for _ in S::inherent_assoc() {} + | ++ + +error[E0277]: `fn() -> std::vec::IntoIter {::assoc_in_trait}` is not an iterator + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:30:14 + | +LL | for _ in ::assoc_in_trait {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ `fn() -> std::vec::IntoIter {::assoc_in_trait}` is not an iterator + | + = help: the trait `Iterator` is not implemented for fn item `fn() -> std::vec::IntoIter {::assoc_in_trait}` + = note: required for `fn() -> std::vec::IntoIter {::assoc_in_trait}` to implement `IntoIterator` +help: use parentheses to call this associated function + | +LL | for _ in ::assoc_in_trait() {} + | ++ + +error[E0277]: `fn() -> impl Iterator {free_fn}` is not an iterator + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:31:14 + | +LL | for _ in free_fn {} + | ^^^^^^^ `fn() -> impl Iterator {free_fn}` is not an iterator + | + = help: the trait `Iterator` is not implemented for fn item `fn() -> impl Iterator {free_fn}` + = note: required for `fn() -> impl Iterator {free_fn}` to implement `IntoIterator` +help: use parentheses to call this function + | +LL | for _ in free_fn() {} + | ++ + +error[E0277]: `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` is not an iterator + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:34:14 + | +LL | for _ in closure {} + | ^^^^^^^ `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` is not an iterator + | + = help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` + = note: required for `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:33:19: 33:21}` to implement `IntoIterator` +help: use parentheses to call this closure + | +LL | for _ in closure() {} + | ++ + +error[E0277]: `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` is not an iterator + --> $DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14 + | +LL | for _ in || vec![1u8].into_iter() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^ `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` is not an iterator + | + = help: the trait `Iterator` is not implemented for closure `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` + = note: required for `{closure@$DIR/suggest-calling-fn-in-for-loop-issue-161564.rs:36:14: 36:16}` to implement `IntoIterator` +help: use parentheses to call this closure + | +LL | for _ in (|| vec![1u8].into_iter())() {} + | + +++ + +error: aborting due to 5 previous errors + +For more information about this error, try `rustc --explain E0277`. From a97dc3135416f40b49ac345074643e3805e1624f Mon Sep 17 00:00:00 2001 From: albab-hasan Date: Tue, 1 Sep 2026 13:17:42 +0600 Subject: [PATCH 13/39] suggest calling a function item that is being dereferenced dereferencing an uncalled function only said the function type cannot be dereferenced. it now suggests the call, gated on the return type actually being dereferenceable. https://github.com/rust-lang/rust/issues/161564#issuecomment-5383101040 --- compiler/rustc_hir_typeck/src/expr.rs | 10 +++ ...uggest-calling-fn-in-deref-issue-161564.rs | 73 ++++++++++++++++++ ...st-calling-fn-in-deref-issue-161564.stderr | 75 +++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.rs create mode 100644 tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.stderr diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 8e3be44caf959..548177a150b9a 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -430,6 +430,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if let Some(sp) = tcx.sess.psess.ambiguous_block_expr_parse.borrow().get(&sp) { err.subdiagnostic(ExprParenthesesNeeded::surrounding(*sp)); } + // The operand may be an uncalled function, in which case it is its return type + // the user meant to dereference. Only suggest the call when that return type is + // itself dereferenceable, mirroring the checks `lookup_derefing` just failed. + self.suggest_fn_call(&mut err, oprnd, oprnd_t, |output| { + output.builtin_deref(true).is_some() + || self.tcx.lang_items().deref_trait().is_some_and(|deref_trait| { + self.type_implements_trait(deref_trait, [output], self.param_env) + .may_apply() + }) + }); Ty::new_error(tcx, err.emit()) }), hir::UnOp::Not => { diff --git a/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.rs b/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.rs new file mode 100644 index 0000000000000..6f1bf4e4589b8 --- /dev/null +++ b/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.rs @@ -0,0 +1,73 @@ +// Dereferencing an uncalled function item should suggest calling it, rather than +// only complaining that the function's own type cannot be dereferenced. + +pub fn ret_ref() -> &'static usize { + &const { 12 } +} + +pub fn ret_val() -> usize { + 12 +} + +pub fn with_args(_: u8) -> &'static usize { + &const { 12 } +} + +pub fn ret_box() -> Box { + Box::new(12) +} + +struct S; + +impl S { + fn assoc() -> &'static usize { + &const { 12 } + } +} + +pub fn fn_item() { + let _a = *ret_ref; + //~^ ERROR type `fn() -> &'static usize {ret_ref}` cannot be dereferenced + //~| HELP use parentheses to call this function +} + +pub fn takes_args() { + let _a = *with_args; + //~^ ERROR type `fn(u8) -> &'static usize {with_args}` cannot be dereferenced + //~| HELP use parentheses to call this function +} + +pub fn assoc_fn() { + let _a = *S::assoc; + //~^ ERROR type `fn() -> &'static usize {S::assoc}` cannot be dereferenced + //~| HELP use parentheses to call this associated function +} + +pub fn overloaded_deref() { + let _a = *ret_box; + //~^ ERROR type `fn() -> Box {ret_box}` cannot be dereferenced + //~| HELP use parentheses to call this function +} + +pub fn fn_pointer() { + let f: fn() -> &'static usize = ret_ref; + let _a = *f; + //~^ ERROR type `fn() -> &'static usize` cannot be dereferenced + //~| HELP use parentheses to call this function pointer +} + +pub fn closure() { + let c = || &const { 12usize }; + let _a = *c; + //~^ ERROR cannot be dereferenced + //~| HELP use parentheses to call this closure +} + +// Negative case: calling this one still would not produce something dereferenceable, +// so no suggestion should be offered. +pub fn not_derefable_when_called() { + let _a = *ret_val; + //~^ ERROR type `fn() -> usize {ret_val}` cannot be dereferenced +} + +fn main() {} diff --git a/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.stderr b/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.stderr new file mode 100644 index 0000000000000..a119a368450c3 --- /dev/null +++ b/tests/ui/suggestions/suggest-calling-fn-in-deref-issue-161564.stderr @@ -0,0 +1,75 @@ +error[E0614]: type `fn() -> &'static usize {ret_ref}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:29:14 + | +LL | let _a = *ret_ref; + | ^^^^^^^^ can't be dereferenced + | +help: use parentheses to call this function + | +LL | let _a = *ret_ref(); + | ++ + +error[E0614]: type `fn(u8) -> &'static usize {with_args}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:35:14 + | +LL | let _a = *with_args; + | ^^^^^^^^^^ can't be dereferenced + | +help: use parentheses to call this function + | +LL | let _a = *with_args(/* u8 */); + | ++++++++++ + +error[E0614]: type `fn() -> &'static usize {S::assoc}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:41:14 + | +LL | let _a = *S::assoc; + | ^^^^^^^^^ can't be dereferenced + | +help: use parentheses to call this associated function + | +LL | let _a = *S::assoc(); + | ++ + +error[E0614]: type `fn() -> Box {ret_box}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:47:14 + | +LL | let _a = *ret_box; + | ^^^^^^^^ can't be dereferenced + | +help: use parentheses to call this function + | +LL | let _a = *ret_box(); + | ++ + +error[E0614]: type `fn() -> &'static usize` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:54:14 + | +LL | let _a = *f; + | ^^ can't be dereferenced + | +help: use parentheses to call this function pointer + | +LL | let _a = *f(); + | ++ + +error[E0614]: type `{closure@$DIR/suggest-calling-fn-in-deref-issue-161564.rs:60:13: 60:15}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:61:14 + | +LL | let _a = *c; + | ^^ can't be dereferenced + | +help: use parentheses to call this closure + | +LL | let _a = *c(); + | ++ + +error[E0614]: type `fn() -> usize {ret_val}` cannot be dereferenced + --> $DIR/suggest-calling-fn-in-deref-issue-161564.rs:69:14 + | +LL | let _a = *ret_val; + | ^^^^^^^^ can't be dereferenced + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0614`. From cb5e3df331e1684fd591c95658c033aed368175d Mon Sep 17 00:00:00 2001 From: Sang-Woo Kim Date: Tue, 1 Sep 2026 08:43:32 +0000 Subject: [PATCH 14/39] std: don't reference `libc::O_NOFOLLOW` on VxWorks in `set_perm_nofollow` VxWorks' libc defines no `O_NOFOLLOW`, so building std for x86_64-wrs-vxworks stopped compiling once `set_perm_nofollow` was consolidated into `sys/fs/unix.rs` without a vxworks guard. VxWorks also has no way to express a no-follow permission change: its `fchmodat` rejects `AT_SYMLINK_NOFOLLOW` with `ENOTSUP`. Return `Unsupported`, matching the existing Android stub. --- library/std/src/sys/fs/unix.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 1045a7b7e2f56..acd2c7f5dfbd1 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1884,6 +1884,13 @@ pub fn set_perm(p: &CStr, perm: FilePermissions) -> io::Result<()> { cvt_r(|| unsafe { libc::chmod(p.as_ptr(), perm.mode) }).map(|_| ()) } +#[cfg(target_os = "vxworks")] +pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> { + // VxWorks has no `O_NOFOLLOW`, and its `fchmodat` rejects + // `AT_SYMLINK_NOFOLLOW` with `ENOTSUP`, so a no-follow chmod is unsupported. + Err(crate::io::ErrorKind::Unsupported.into()) +} + #[cfg(target_os = "android")] pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> { // Currently Android seems to be having inconsistent behavior with fchmodat @@ -1896,7 +1903,7 @@ pub fn set_perm_nofollow(_p: &CStr, _perm: FilePermissions) -> io::Result<()> { Err(crate::io::ErrorKind::Unsupported.into()) } -#[cfg(not(target_os = "android"))] +#[cfg(not(any(target_os = "android", target_os = "vxworks")))] pub fn set_perm_nofollow(p: &CStr, perm: FilePermissions) -> io::Result<()> { #[inline] /// Helper function for fallback open with `O_NOFOLLOW` + `fchmod` behavior From 8e85fcf7df9b75d770f94e887b6c10188e6a3085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 30 Jul 2026 10:14:45 +0200 Subject: [PATCH 15/39] Bless bootstrap tests And only include the target name when rendering test metadata, to avoid including filenames in it. --- src/bootstrap/src/core/builder/tests.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 1f08ee9c11864..6af25c8a1ce2f 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -1941,6 +1941,8 @@ mod snapshot { [test] compiletest-coverage 1 [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 [test] compiletest-codegen-llvm 1 [test] compiletest-codegen-units 1 [test] compiletest-assembly-llvm 1 @@ -2122,6 +2124,9 @@ mod snapshot { [test] compiletest-coverage 2 [build] rustc 2 -> std 2 [test] compiletest-mir-opt 2 + [build] rustc 1 -> std 1 + [build] rustc 2 -> std 2 + [test] compiletest-mir-opt 2 [test] compiletest-codegen-llvm 2 [test] compiletest-codegen-units 2 [test] compiletest-assembly-llvm 2 @@ -3184,7 +3189,7 @@ fn render_metadata(metadata: &StepMetadata, config: &RenderConfig) -> String { } fn normalize_target(target: TargetSelection, config: &RenderConfig) -> String { - let mut target = target.to_string(); + let mut target = target.triple.to_string(); if config.normalize_host { target = target.replace(&host_target(), "host"); } From bdc7aeecc4eb62c643d62469fd1766c0bda9e934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 09:59:58 +0200 Subject: [PATCH 16/39] Generalize `MirOptPanicAbortSyntheticTarget` to `SyntheticTargetWithPanicStrategy` --- .../src/core/build_steps/synthetic_targets.rs | 26 ++++++++++++++++--- src/bootstrap/src/core/build_steps/test.rs | 8 +++--- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 2b5039214f62c..75999c1fa355b 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -12,17 +12,37 @@ use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct MirOptPanicAbortSyntheticTarget { +pub(crate) enum PanicStrategy { + Unwind, + Abort, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct SyntheticTargetWithPanicStrategy { pub(crate) compiler: Compiler, pub(crate) base: TargetSelection, + pub(crate) strategy: PanicStrategy, +} + +impl SyntheticTargetWithPanicStrategy { + pub(crate) fn panic_abort(compiler: Compiler, base: TargetSelection) -> Self { + Self { compiler, base, strategy: PanicStrategy::Abort } + } + pub(crate) fn panic_unwind(compiler: Compiler, base: TargetSelection) -> Self { + Self { compiler, base, strategy: PanicStrategy::Unwind } + } } -impl Step for MirOptPanicAbortSyntheticTarget { +impl Step for SyntheticTargetWithPanicStrategy { type Output = TargetSelection; fn run(self, builder: &Builder<'_>) -> Self::Output { + let strategy = match self.strategy { + PanicStrategy::Unwind => "unwind", + PanicStrategy::Abort => "abort", + }; create_synthetic_target(builder, self.compiler, "miropt-abort", self.base, |spec| { - spec.insert("panic-strategy".into(), "abort".into()); + spec.insert("panic-strategy".into(), strategy.into()); }) } } diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 5f6803b9ba170..19122d558c03c 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -22,7 +22,7 @@ use crate::core::build_steps::format::InternalRustfmt; use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; -use crate::core::build_steps::synthetic_targets::MirOptPanicAbortSyntheticTarget; +use crate::core::build_steps::synthetic_targets::SyntheticTargetWithPanicStrategy; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; use crate::core::build_steps::tool::{ @@ -2217,10 +2217,8 @@ impl CommandLineStep for MirOpt { for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { let target = TargetSelection::from_user(target); - let panic_abort_target = builder.ensure(MirOptPanicAbortSyntheticTarget { - compiler: self.compiler, - base: target, - }); + let panic_abort_target = builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(self.compiler, target)); run(panic_abort_target); } } From 1119245edbea2d5fa1ffe32281453bc2982c3839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 10:11:25 +0200 Subject: [PATCH 17/39] Create targets for `mir-opt` tests explicitly and use the minimal set of targets to check --- .../src/core/build_steps/synthetic_targets.rs | 30 +++-- src/bootstrap/src/core/build_steps/test.rs | 108 +++++++++++++----- src/bootstrap/src/core/builder/tests.rs | 43 +++++++ 3 files changed, 143 insertions(+), 38 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 75999c1fa355b..4afbdff464e34 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -69,16 +69,7 @@ fn create_synthetic_target( return TargetSelection::create_synthetic(&name, path.to_str().unwrap()); } - let mut cmd = builder.rustc_cmd(compiler); - cmd.arg("--target").arg(base.rustc_target_arg()); - cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); - - // If `rust.channel` is set to either beta or stable, rustc will complain that - // we cannot use nightly features. So `RUSTC_BOOTSTRAP` is needed here. - cmd.env("RUSTC_BOOTSTRAP", "1"); - - let output = cmd.run_capture(builder).stdout(); - let mut spec: serde_json::Value = serde_json::from_slice(output.as_bytes()).unwrap(); + let mut spec = get_target_specs(builder, compiler, base); let spec_map = spec.as_object_mut().unwrap(); // The `is-builtin` attribute of a spec needs to be removed, otherwise rustc will complain. @@ -89,3 +80,22 @@ fn create_synthetic_target( std::fs::write(&path, serde_json::to_vec_pretty(&spec).unwrap()).unwrap(); TargetSelection::create_synthetic(&name, path.to_str().unwrap()) } + +/// Get the JSON target specs from the given compiler. +pub fn get_target_specs( + builder: &Builder<'_>, + compiler: Compiler, + target: TargetSelection, +) -> serde_json::Value { + let mut cmd = builder.rustc_cmd(compiler); + cmd.arg("--target").arg(target.rustc_target_arg()); + cmd.args(["-Zunstable-options", "--print", "target-spec-json"]); + + // If `rust.channel` is set to either beta or stable, rustc will complain that + // we cannot use nightly features. So `RUSTC_BOOTSTRAP` is needed here. + cmd.env("RUSTC_BOOTSTRAP", "1"); + + let output = cmd.cached().run_capture(builder).stdout(); + let spec: serde_json::Value = serde_json::from_slice(output.as_bytes()).unwrap(); + spec +} diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 19122d558c03c..0d36797e7b2f8 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -22,7 +22,9 @@ use crate::core::build_steps::format::InternalRustfmt; use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; -use crate::core::build_steps::synthetic_targets::SyntheticTargetWithPanicStrategy; +use crate::core::build_steps::synthetic_targets::{ + SyntheticTargetWithPanicStrategy, get_target_specs, +}; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; use crate::core::build_steps::tool::{ @@ -2169,8 +2171,8 @@ test!(CoverageRunRustdoc { // For the mir-opt suite we do not use macros, as we need custom behavior when blessing. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MirOpt { - pub compiler: Compiler, - pub target: TargetSelection, + compiler: Compiler, + target: TargetSelection, } impl CommandLineStep for MirOpt { @@ -2186,43 +2188,93 @@ impl CommandLineStep for MirOpt { fn make_run(run: RunConfig<'_>) { let compiler = run.builder.compiler(run.builder.top_stage, run.build_triple()); - run.builder.ensure(MirOpt { compiler, target: run.target }); - } - fn run(self, builder: &Builder<'_>) { - let run = |target| { - builder.ensure(Compiletest { - test_compiler: self.compiler, - target, - mode: CompiletestMode::MirOpt, - suite: "mir-opt", - path: "tests/mir-opt", - compare_mode: None, - }) - }; + // The mir-opt tests check four distinct configurations, the cross-product of the + // following two axes: + // - Bit-width: 32-bit and 64-bit + // - Panic strategy: unwind and abort - run(self.target); + // Here we generate several configurations of this step to evaluate multiple targets. + let targets = if run.builder.config.cmd.bless() { + // When blessing, we generate a fixed set of 4 targets that cover all the + // possible combinations. This selection covers all our tier 1 operating systems and + // architectures using only tier 1 targets. - // Run more targets with `--bless`. But we always run the host target first, since some - // tests use very specific `only` clauses that are not covered by the target set below. - if builder.config.cmd.bless() { - // All that we really need to do is cover all combinations of 32/64-bit and unwind/abort, - // but while we're at it we might as well flex our cross-compilation support. This - // selection covers all our tier 1 operating systems and architectures using only tier - // 1 targets. + // We also include the host target, since some tests use very specific `only` clauses + // that are not covered by the target set below. + let mut targets = vec![run.target]; + + // 64-bit and 32-bit panic=unwind for target in ["aarch64-unknown-linux-gnu", "i686-pc-windows-msvc"] { - run(TargetSelection::from_user(target)); + targets.push(TargetSelection::from_user(target)); } + // 64-bit and 32-bit panic=abort for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { let target = TargetSelection::from_user(target); - let panic_abort_target = builder - .ensure(SyntheticTargetWithPanicStrategy::panic_abort(self.compiler, target)); - run(panic_abort_target); + let panic_abort_target = run + .builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, target)); + targets.push(panic_abort_target); + } + targets + } else { + // When not blessing, we could also test all four configurations. But that would make + // local tests quite slow. So instead, we check the current target, and then the + // current target with switched panic strategy. + // On CI, we should be running this test for both 32-bit and 64-bit targets, so together + // this should check all possible configurations on CI. + + // The complicated thing here is how to figure out the panic strategy of the current + // target. In theory, we could just assume that in most situations, the target is + // panic=unwind, and force generation of panic=abort. But to ensure that we do this + // properly, we actually query the compiler to figure out the panic strategy, and then + // generate a synthetic target with the opposite strategy. + if !run.builder.config.dry_run() { + let target_specs = get_target_specs(run.builder, compiler, run.target); + let panic_strategy = target_specs + .as_object() + .and_then(|obj| obj.get("panic-strategy")) + .and_then(|v| v.as_str()) + // The default panic strategy is unwind + .unwrap_or("unwind"); + let synthetic_target = if panic_strategy == "unwind" { + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) + } else { + run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_unwind( + compiler, run.target, + )) + }; + vec![run.target, synthetic_target] + } else { + // Note: in a dry run, we just hardcode the other target to be panic=abort, + // so that we still see two targets in snapshot tests. + vec![ + run.target, + run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_abort( + compiler, run.target, + )), + ] } + }; + + for target in targets { + run.builder.ensure(MirOpt { compiler, target }); } } + + fn run(self, builder: &Builder<'_>) { + builder.ensure(Compiletest { + test_compiler: self.compiler, + target: self.target, + mode: CompiletestMode::MirOpt, + suite: "mir-opt", + path: "tests/mir-opt", + compare_mode: None, + }); + } } /// Executes the `compiletest` tool to run a suite of tests. diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 6af25c8a1ce2f..e023d9d7f2b16 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2394,6 +2394,49 @@ mod snapshot { "); } + #[test] + fn test_mir_opt() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + prepare_test_config(&ctx) + .path("tests/mir-opt") + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + "); + } + + #[test] + fn test_mir_opt_bless() { + let ctx = TestCtx::new(); + insta::assert_snapshot!( + prepare_test_config(&ctx) + .path("tests/mir-opt") + .arg("--bless") + .targets(&[TEST_TRIPLE_1]) + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + [build] rustc 1 -> std 1 + [test] compiletest-mir-opt 1 + "); + } + #[test] fn doc_all() { let ctx = TestCtx::new(); From 2b971b6ea2b5a18da2e5f4f8ccdf24445ba5183f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 10:49:17 +0200 Subject: [PATCH 18/39] Fix host normalization --- src/bootstrap/src/core/builder/tests.rs | 27 +++++++++++++++---------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index e023d9d7f2b16..895b1849efdf4 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2416,23 +2416,28 @@ mod snapshot { let ctx = TestCtx::new(); insta::assert_snapshot!( prepare_test_config(&ctx) - .path("tests/mir-opt") .arg("--bless") .targets(&[TEST_TRIPLE_1]) - .render_steps(), @" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> Compiletest 1 - [build] rustc 1 -> std 1 + .path("tests/mir-opt") + .get_steps() + // When blessing, the step executes for a pinned set of targets, so we cannot + // normalize here. + .render_with(RenderConfig { + normalize_host: false + }), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 "); } From 0ef414a3a9dd9c5aaed07824f2f9ba07b6c57551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Aug 2026 11:15:16 +0200 Subject: [PATCH 19/39] Bless at most four individual targets --- .../src/core/build_steps/synthetic_targets.rs | 2 +- src/bootstrap/src/core/build_steps/test.rs | 83 +++++++++++-------- src/bootstrap/src/core/builder/tests.rs | 27 +++--- 3 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 4afbdff464e34..04b815743bafd 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -11,7 +11,7 @@ use crate::core::builder::{Builder, Step}; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum PanicStrategy { Unwind, Abort, diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs index 0d36797e7b2f8..34404b85000eb 100644 --- a/src/bootstrap/src/core/build_steps/test.rs +++ b/src/bootstrap/src/core/build_steps/test.rs @@ -23,7 +23,7 @@ use crate::core::build_steps::gcc::{Gcc, GccTargetPair, add_cg_gcc_cargo_flags}; use crate::core::build_steps::llvm::get_llvm_version; use crate::core::build_steps::run::{get_completion_paths, get_help_path}; use crate::core::build_steps::synthetic_targets::{ - SyntheticTargetWithPanicStrategy, get_target_specs, + PanicStrategy, SyntheticTargetWithPanicStrategy, get_target_specs, }; use crate::core::build_steps::test::compiletest::CompiletestMode; use crate::core::build_steps::test::failed_tests::{RecordFailedTests, SetupFailedTestsFile}; @@ -2194,6 +2194,31 @@ impl CommandLineStep for MirOpt { // - Bit-width: 32-bit and 64-bit // - Panic strategy: unwind and abort + // Return the bitwidth and panic strategy of the default (usually host) target + let get_bitwidth_and_panic_strategy = || -> (u64, PanicStrategy) { + if run.builder.config.dry_run() { + return (64, PanicStrategy::Unwind); + } + + let specs = get_target_specs(run.builder, compiler, run.target); + let specs = specs.as_object(); + let bitwidth = specs + .and_then(|obj| obj.get("target-pointer-width")) + .and_then(|v| v.as_i64()) + .map(|v| v as u64) + .unwrap_or(64); + let panic_strategy = specs + .and_then(|obj| obj.get("panic-strategy")) + .and_then(|v| v.as_str()) + .map(|v| match v { + "unwind" => PanicStrategy::Unwind, + _ => PanicStrategy::Abort, + }) + // The default panic strategy is unwind + .unwrap_or(PanicStrategy::Unwind); + (bitwidth, panic_strategy) + }; + // Here we generate several configurations of this step to evaluate multiple targets. let targets = if run.builder.config.cmd.bless() { // When blessing, we generate a fixed set of 4 targets that cover all the @@ -2203,22 +2228,32 @@ impl CommandLineStep for MirOpt { // We also include the host target, since some tests use very specific `only` clauses // that are not covered by the target set below. - let mut targets = vec![run.target]; + let (bitwidth, strategy) = get_bitwidth_and_panic_strategy(); + let mut targets = vec![(bitwidth, strategy, run.target)]; // 64-bit and 32-bit panic=unwind - for target in ["aarch64-unknown-linux-gnu", "i686-pc-windows-msvc"] { - targets.push(TargetSelection::from_user(target)); + for (bitwidth, target) in + [(64, "aarch64-unknown-linux-gnu"), (32, "i686-pc-windows-msvc")] + { + targets.push((bitwidth, PanicStrategy::Unwind, TargetSelection::from_user(target))); } // 64-bit and 32-bit panic=abort - for target in ["x86_64-apple-darwin", "i686-unknown-linux-musl"] { + for (bitwidth, target) in [(64, "x86_64-apple-darwin"), (32, "i686-unknown-linux-musl")] + { let target = TargetSelection::from_user(target); let panic_abort_target = run .builder .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, target)); - targets.push(panic_abort_target); + targets.push((bitwidth, PanicStrategy::Abort, panic_abort_target)); } - targets + // This is a small optimization for local blessing. + // If we figure out that the host target already has a given bitwidth/panic strategy + // combination, we do not add the fixed targets to the list. + let mut unique = HashSet::new(); + targets.retain(|(bitwidth, strategy, _)| unique.insert((*bitwidth, *strategy))); + + targets.into_iter().map(|(_, _, target)| target).collect() } else { // When not blessing, we could also test all four configurations. But that would make // local tests quite slow. So instead, we check the current target, and then the @@ -2231,33 +2266,15 @@ impl CommandLineStep for MirOpt { // panic=unwind, and force generation of panic=abort. But to ensure that we do this // properly, we actually query the compiler to figure out the panic strategy, and then // generate a synthetic target with the opposite strategy. - if !run.builder.config.dry_run() { - let target_specs = get_target_specs(run.builder, compiler, run.target); - let panic_strategy = target_specs - .as_object() - .and_then(|obj| obj.get("panic-strategy")) - .and_then(|v| v.as_str()) - // The default panic strategy is unwind - .unwrap_or("unwind"); - let synthetic_target = if panic_strategy == "unwind" { - run.builder - .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) - } else { - run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_unwind( - compiler, run.target, - )) - }; - vec![run.target, synthetic_target] + let panic_strategy = get_bitwidth_and_panic_strategy().1; + let synthetic_target = if panic_strategy == PanicStrategy::Unwind { + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_abort(compiler, run.target)) } else { - // Note: in a dry run, we just hardcode the other target to be panic=abort, - // so that we still see two targets in snapshot tests. - vec![ - run.target, - run.builder.ensure(SyntheticTargetWithPanicStrategy::panic_abort( - compiler, run.target, - )), - ] - } + run.builder + .ensure(SyntheticTargetWithPanicStrategy::panic_unwind(compiler, run.target)) + }; + vec![run.target, synthetic_target] }; for target in targets { diff --git a/src/bootstrap/src/core/builder/tests.rs b/src/bootstrap/src/core/builder/tests.rs index 895b1849efdf4..021587074d4cb 100644 --- a/src/bootstrap/src/core/builder/tests.rs +++ b/src/bootstrap/src/core/builder/tests.rs @@ -2417,27 +2417,22 @@ mod snapshot { insta::assert_snapshot!( prepare_test_config(&ctx) .arg("--bless") + .hosts(&[TEST_TRIPLE_1]) + .arg("--build") + .arg(TEST_TRIPLE_1) .targets(&[TEST_TRIPLE_1]) .path("tests/mir-opt") - .get_steps() - // When blessing, the step executes for a pinned set of targets, so we cannot - // normalize here. - .render_with(RenderConfig { - normalize_host: false - }), @" - [build] llvm - [build] rustc 0 -> rustc 1 - [build] rustc 1 -> std 1 - [build] rustc 0 -> Compiletest 1 - [build] rustc 1 -> std 1 + .render_steps(), @" + [build] llvm + [build] rustc 0 -> rustc 1 + [build] rustc 1 -> std 1 + [build] rustc 0 -> Compiletest 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 - [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 - [build] rustc 1 -> std 1 + [build] rustc 1 -> std 1 [test] compiletest-mir-opt 1 "); } From 81c5ef2a9bae97ed8a4bdce21d6551c0a65a7949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 10 Aug 2026 11:03:52 +0200 Subject: [PATCH 20/39] Do not call `configure_linker` for synthetic targets --- src/bootstrap/src/core/builder/cargo.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 9eff50772a8f2..482eb6a4cdb00 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -178,7 +178,11 @@ impl Cargo { // No need to configure the target linker for these command types. Kind::Clean | Kind::Check | Kind::Format | Kind::Setup => {} _ => { - cargo.configure_linker(builder); + // Do not configure the linker for synthetic targets, as we won't have cc set up + // for them. + if !target.is_synthetic() { + cargo.configure_linker(builder); + } } } From 73c955ce50c45104596bdabb28297b3854eb5dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 20 Aug 2026 08:50:32 +0200 Subject: [PATCH 21/39] Add `needs-deterministic-layouts` flag --- tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs index 5534a45f19d64..8593a322ad363 100644 --- a/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs +++ b/tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs @@ -1,4 +1,6 @@ //@ test-mir-pass: GVN +// layout randomization affects the alloc output +//@ needs-deterministic-layouts //@ compile-flags: -Zinline-mir --crate-type lib // EMIT_MIR_FOR_EACH_BIT_WIDTH // EMIT_MIR_FOR_EACH_PANIC_STRATEGY From b1ff4d6fe9a0dafc004a2aaec664f646303845e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 1 Sep 2026 14:46:02 +0200 Subject: [PATCH 22/39] Add comments --- src/bootstrap/src/core/build_steps/synthetic_targets.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/bootstrap/src/core/build_steps/synthetic_targets.rs b/src/bootstrap/src/core/build_steps/synthetic_targets.rs index 04b815743bafd..2c35b39287d70 100644 --- a/src/bootstrap/src/core/build_steps/synthetic_targets.rs +++ b/src/bootstrap/src/core/build_steps/synthetic_targets.rs @@ -11,6 +11,8 @@ use crate::core::builder::{Builder, Step}; use crate::core::compiler::Compiler; use crate::core::config::TargetSelection; +/// Note that this currently only contains panic strategies that we somehow use in bootstrap, not +/// all possible strategires supported by rustc. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum PanicStrategy { Unwind, @@ -82,6 +84,7 @@ fn create_synthetic_target( } /// Get the JSON target specs from the given compiler. +/// Note that the set of targets will differ between the stage0 and stage1+ (in-tree) compiler! pub fn get_target_specs( builder: &Builder<'_>, compiler: Compiler, From 5bc30a90e37c632e29de89f67688d0ed14a12552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Tue, 1 Sep 2026 15:53:04 +0200 Subject: [PATCH 23/39] Bless test --- ...er.enumerated_loop.runtime-optimized.after.panic-abort.mir | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir index 549af7af4d888..b42087b5c822d 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.runtime-optimized.after.panic-abort.mir @@ -21,7 +21,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { debug x => _34; } scope 18 (inlined > as Iterator>::next) { - let mut _22: std::option::Option; + let mut _22: std::option::Option; let mut _27: std::option::Option<&T>; let mut _30: (usize, bool); let mut _31: (usize, &T); @@ -32,7 +32,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { } scope 20 { scope 21 { - scope 27 (inlined as FromResidual>>::from_residual) { + scope 27 (inlined as FromResidual>>::from_residual) { let mut _21: isize; let mut _23: bool; } From 9ed2e0c0cac7ce208faf5055b0452abdc04167a0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 1 Sep 2026 20:44:08 +0200 Subject: [PATCH 24/39] reformat itanium mangling test to be more readable --- ...data-id-itanium-cxx-abi-primitive-types.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs index 93845d0519541..cb8eb13ac75ed 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs @@ -15,114 +15,133 @@ pub fn foo2(_: (), _: c_void) {} // CHECK: define{{.*}}4foo2{{.*}}!type ![[TYPE1:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo3(_: (), _: c_void, _: c_void) {} // CHECK: define{{.*}}4foo3{{.*}}!type ![[TYPE2:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo4(_: *mut ()) {} // CHECK: define{{.*}}4foo4{{.*}}!type ![[TYPE4:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo5(_: *mut (), _: *mut c_void) {} // CHECK: define{{.*}}4foo5{{.*}}!type ![[TYPE5:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo6(_: *mut (), _: *mut c_void, _: *mut c_void) {} // CHECK: define{{.*}}4foo6{{.*}}!type ![[TYPE6:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo7(_: *const ()) {} // CHECK: define{{.*}}4foo7{{.*}}!type ![[TYPE7:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo8(_: *const (), _: *const c_void) {} // CHECK: define{{.*}}4foo8{{.*}}!type ![[TYPE8:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo9(_: *const (), _: *const c_void, _: *const c_void) {} // CHECK: define{{.*}}4foo9{{.*}}!type ![[TYPE9:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo10(_: bool) {} // CHECK: define{{.*}}5foo10{{.*}}!type ![[TYPE10:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo11(_: bool, _: bool) {} // CHECK: define{{.*}}5foo11{{.*}}!type ![[TYPE11:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo12(_: bool, _: bool, _: bool) {} // CHECK: define{{.*}}5foo12{{.*}}!type ![[TYPE12:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo13(_: i8) {} // CHECK: define{{.*}}5foo13{{.*}}!type ![[TYPE13:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo14(_: i8, _: i8) {} // CHECK: define{{.*}}5foo14{{.*}}!type ![[TYPE14:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo15(_: i8, _: i8, _: i8) {} // CHECK: define{{.*}}5foo15{{.*}}!type ![[TYPE15:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo16(_: i16) {} // CHECK: define{{.*}}5foo16{{.*}}!type ![[TYPE16:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo17(_: i16, _: i16) {} // CHECK: define{{.*}}5foo17{{.*}}!type ![[TYPE17:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo18(_: i16, _: i16, _: i16) {} // CHECK: define{{.*}}5foo18{{.*}}!type ![[TYPE18:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo19(_: i32) {} // CHECK: define{{.*}}5foo19{{.*}}!type ![[TYPE19:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo20(_: i32, _: i32) {} // CHECK: define{{.*}}5foo20{{.*}}!type ![[TYPE20:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo21(_: i32, _: i32, _: i32) {} // CHECK: define{{.*}}5foo21{{.*}}!type ![[TYPE21:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo22(_: i64) {} // CHECK: define{{.*}}5foo22{{.*}}!type ![[TYPE22:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo23(_: i64, _: i64) {} // CHECK: define{{.*}}5foo23{{.*}}!type ![[TYPE23:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo24(_: i64, _: i64, _: i64) {} // CHECK: define{{.*}}5foo24{{.*}}!type ![[TYPE24:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo25(_: i128) {} // CHECK: define{{.*}}5foo25{{.*}}!type ![[TYPE25:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo26(_: i128, _: i128) {} // CHECK: define{{.*}}5foo26{{.*}}!type ![[TYPE26:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo27(_: i128, _: i128, _: i128) {} // CHECK: define{{.*}}5foo27{{.*}}!type ![[TYPE27:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo28(_: isize) {} // CHECK: define{{.*}}5foo28{{.*}}!type ![[TYPE28:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo29(_: isize, _: isize) {} // CHECK: define{{.*}}5foo29{{.*}}!type ![[TYPE29:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo30(_: isize, _: isize, _: isize) {} // CHECK: define{{.*}}5foo30{{.*}}!type ![[TYPE30:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo31(_: u8) {} // CHECK: define{{.*}}5foo31{{.*}}!type ![[TYPE31:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo32(_: u8, _: u8) {} // CHECK: define{{.*}}5foo32{{.*}}!type ![[TYPE32:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo33(_: u8, _: u8, _: u8) {} // CHECK: define{{.*}}5foo33{{.*}}!type ![[TYPE33:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo34(_: u16) {} // CHECK: define{{.*}}5foo34{{.*}}!type ![[TYPE34:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo35(_: u16, _: u16) {} // CHECK: define{{.*}}5foo35{{.*}}!type ![[TYPE35:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo36(_: u16, _: u16, _: u16) {} // CHECK: define{{.*}}5foo36{{.*}}!type ![[TYPE36:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo37(_: u32) {} // CHECK: define{{.*}}5foo37{{.*}}!type ![[TYPE37:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo38(_: u32, _: u32) {} // CHECK: define{{.*}}5foo38{{.*}}!type ![[TYPE38:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo39(_: u32, _: u32, _: u32) {} // CHECK: define{{.*}}5foo39{{.*}}!type ![[TYPE39:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo40(_: u64) {} // CHECK: define{{.*}}5foo40{{.*}}!type ![[TYPE40:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo41(_: u64, _: u64) {} // CHECK: define{{.*}}5foo41{{.*}}!type ![[TYPE41:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo42(_: u64, _: u64, _: u64) {} // CHECK: define{{.*}}5foo42{{.*}}!type ![[TYPE42:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo43(_: u128) {} // CHECK: define{{.*}}5foo43{{.*}}!type ![[TYPE43:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo44(_: u128, _: u128) {} // CHECK: define{{.*}}5foo44{{.*}}!type ![[TYPE44:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo45(_: u128, _: u128, _: u128) {} // CHECK: define{{.*}}5foo45{{.*}}!type ![[TYPE45:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo46(_: usize) {} // CHECK: define{{.*}}5foo46{{.*}}!type ![[TYPE46:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo47(_: usize, _: usize) {} // CHECK: define{{.*}}5foo47{{.*}}!type ![[TYPE47:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo48(_: usize, _: usize, _: usize) {} // CHECK: define{{.*}}5foo48{{.*}}!type ![[TYPE48:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo49(_: f32) {} // CHECK: define{{.*}}5foo49{{.*}}!type ![[TYPE49:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo50(_: f32, _: f32) {} // CHECK: define{{.*}}5foo50{{.*}}!type ![[TYPE50:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo51(_: f32, _: f32, _: f32) {} // CHECK: define{{.*}}5foo51{{.*}}!type ![[TYPE51:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo52(_: f64) {} // CHECK: define{{.*}}5foo52{{.*}}!type ![[TYPE52:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo53(_: f64, _: f64) {} // CHECK: define{{.*}}5foo53{{.*}}!type ![[TYPE53:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo54(_: f64, _: f64, _: f64) {} // CHECK: define{{.*}}5foo54{{.*}}!type ![[TYPE54:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo55(_: char) {} // CHECK: define{{.*}}5foo55{{.*}}!type ![[TYPE55:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo56(_: char, _: char) {} // CHECK: define{{.*}}5foo56{{.*}}!type ![[TYPE56:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo57(_: char, _: char, _: char) {} // CHECK: define{{.*}}5foo57{{.*}}!type ![[TYPE57:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + pub fn foo58(_: &str) {} // CHECK: define{{.*}}5foo58{{.*}}!type ![[TYPE58:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} pub fn foo59(_: &str, _: &str) {} From 7a44484407077776e30120f5e72cdef2820e8856 Mon Sep 17 00:00:00 2001 From: Leonard Chan Date: Fri, 24 Jul 2026 23:03:20 +0000 Subject: [PATCH 25/39] fuchsia: Add safestack as a supported sanitizer for x86_64 fuchsia Make it also enabled by default just like it is for clang. --- .../rustc_target/src/spec/targets/x86_64_unknown_fuchsia.rs | 4 +++- .../stack-protector/stack-protector-target-support.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_target/src/spec/targets/x86_64_unknown_fuchsia.rs b/compiler/rustc_target/src/spec/targets/x86_64_unknown_fuchsia.rs index dbff5e30828a9..b8fb1fb6d3a52 100644 --- a/compiler/rustc_target/src/spec/targets/x86_64_unknown_fuchsia.rs +++ b/compiler/rustc_target/src/spec/targets/x86_64_unknown_fuchsia.rs @@ -9,7 +9,9 @@ pub(crate) fn target() -> Target { base.features = "+cmpxchg16b,+lahfsahf,+popcnt,+sse3,+sse4.1,+sse4.2,+ssse3".into(); base.max_atomic_width = Some(128); base.stack_probes = StackProbeType::Inline; - base.supported_sanitizers = SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::LEAK; + base.supported_sanitizers = + SanitizerSet::ADDRESS | SanitizerSet::CFI | SanitizerSet::LEAK | SanitizerSet::SAFESTACK; + base.default_sanitizers = SanitizerSet::SAFESTACK; base.supports_xray = true; Target { diff --git a/tests/assembly-llvm/stack-protector/stack-protector-target-support.rs b/tests/assembly-llvm/stack-protector/stack-protector-target-support.rs index 9f182985d1573..f883e7ac8691f 100644 --- a/tests/assembly-llvm/stack-protector/stack-protector-target-support.rs +++ b/tests/assembly-llvm/stack-protector/stack-protector-target-support.rs @@ -177,13 +177,15 @@ //@ compile-flags: -C opt-level=2 #![crate_type = "lib"] -#![feature(no_core, lang_items)] +#![feature(no_core, lang_items, sanitize)] #![crate_type = "lib"] #![no_core] extern crate minicore; use minicore::*; +// We only do this because we can't disable default sanitizers via compile-flags. +#[sanitize(safestack = "off")] #[no_mangle] pub fn foo() { // CHECK: foo{{:|()}} From 1d08a6b9ff707d19caf72d8ba427ec2ffa399739 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Tue, 1 Sep 2026 12:49:07 -0700 Subject: [PATCH 26/39] Prefer `LLVMGetVersion` for runtime info Our `LLVMRustVersion*` functions get hard-coded `LLVM_VERSION_*` values when we build `RustWrapper.cpp`, but this could be different than the actual LLVM library at runtime. This should never happen with toolchains from `rustup`, but with external LLVM in a distro build, for example, `rustc` and `LLVM` can be upgraded independently. Most of the time when we check the LLVM version, we're only looking at the major version anyway, and we already assert that these are equal in `configure_llvm`. However, for anything that does check the minor or patch version too, the runtime version is probably more relevant. --- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 8 ++++++-- compiler/rustc_codegen_llvm/src/llvm_util.rs | 11 ++++++----- compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp | 4 ---- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 684bba7a717db..c20b4ccd776da 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -2172,9 +2172,13 @@ unsafe extern "C" { pub(crate) safe fn LLVMRustCoverageMappingVersion() -> u32; pub(crate) fn LLVMRustDebugMetadataVersion() -> u32; + + /// Returns the LLVM major version that the compiler was built with. + /// + /// Note that this is hard-coded as `LLVM_VERSION_MAJOR` when `RustWrapper.cpp` is built. This + /// could be different than what the runtime LLVM library reports in `LLVMGetVersion`, so we + /// assert their equality in `configure_llvm`. pub(crate) fn LLVMRustVersionMajor() -> u32; - pub(crate) fn LLVMRustVersionMinor() -> u32; - pub(crate) fn LLVMRustVersionPatch() -> u32; /// Add LLVM module flags. /// diff --git a/compiler/rustc_codegen_llvm/src/llvm_util.rs b/compiler/rustc_codegen_llvm/src/llvm_util.rs index 82ddcca3e1530..a5441b1ef1135 100644 --- a/compiler/rustc_codegen_llvm/src/llvm_util.rs +++ b/compiler/rustc_codegen_llvm/src/llvm_util.rs @@ -50,10 +50,7 @@ unsafe fn configure_llvm(sess: &Session) { // Check to ensure we're running against the correct LLVM version. unsafe { - let mut llvm_major = 0; - let mut llvm_minor = 0; - let mut llvm_patch = 0; - llvm::LLVMGetVersion(&mut llvm_major, &mut llvm_minor, &mut llvm_patch); + let (llvm_major, llvm_minor, llvm_patch) = get_version(); let expected_version = llvm::LLVMRustVersionMajor(); if llvm_major != expected_version { sess.dcx().emit_fatal(diagnostics::LlvmVersionMismatch { @@ -465,7 +462,11 @@ pub(crate) fn print_version() { pub(crate) fn get_version() -> (u32, u32, u32) { // Can be called without initializing LLVM unsafe { - (llvm::LLVMRustVersionMajor(), llvm::LLVMRustVersionMinor(), llvm::LLVMRustVersionPatch()) + let mut llvm_major = 0; + let mut llvm_minor = 0; + let mut llvm_patch = 0; + llvm::LLVMGetVersion(&mut llvm_major, &mut llvm_minor, &mut llvm_patch); + (llvm_major, llvm_minor, llvm_patch) } } diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 189296dc9c4c8..6947c4766eccf 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -885,10 +885,6 @@ extern "C" uint32_t LLVMRustDebugMetadataVersion() { return DEBUG_METADATA_VERSION; } -extern "C" uint32_t LLVMRustVersionPatch() { return LLVM_VERSION_PATCH; } - -extern "C" uint32_t LLVMRustVersionMinor() { return LLVM_VERSION_MINOR; } - extern "C" uint32_t LLVMRustVersionMajor() { return LLVM_VERSION_MAJOR; } // FFI equivalent of LLVM's `llvm::Module::ModFlagBehavior`. From 45b97ea0cc2cf6b6d8d0a9bbb1906dc8d29a53d9 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 1 Sep 2026 21:39:11 +0200 Subject: [PATCH 27/39] itanium mangling: use minicore --- ...ype-metadata-id-itanium-cxx-abi-primitive-types.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs index cb8eb13ac75ed..52204755f47dd 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs @@ -1,13 +1,18 @@ // Verifies that type metadata identifiers for functions are emitted correctly // for primitive types. // +//@ add-minicore //@ needs-sanitizer-cfi -//@ compile-flags: -Clto -Cno-prepopulate-passes -Copt-level=0 -Zsanitizer=cfi -Ctarget-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer +//@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 +//@ compile-flags: -Clto -Zsanitizer=cfi -Ctarget-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer +//@ minicore-compile-flags: -Ccodegen-units=1 #![crate_type = "lib"] +#![feature(no_core)] +#![no_core] -extern crate core; -use core::ffi::*; +extern crate minicore; +use minicore::*; pub fn foo1(_: ()) {} // CHECK: define{{.*}}4foo1{{.*}}!type ![[TYPE1:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} From bb0bcf25cca2f9996c5d374f4fd8d914965a03f6 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 1 Sep 2026 21:04:19 +0200 Subject: [PATCH 28/39] add itanium mangling test for `f16` and `f128` --- ...data-id-itanium-cxx-abi-primitive-types.rs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs index 52204755f47dd..59e794df69990 100644 --- a/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs +++ b/tests/codegen-llvm/sanitizer/cfi/emit-type-metadata-id-itanium-cxx-abi-primitive-types.rs @@ -3,12 +3,12 @@ // //@ add-minicore //@ needs-sanitizer-cfi -//@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 +//@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 -C link-dead-code //@ compile-flags: -Clto -Zsanitizer=cfi -Ctarget-feature=-crt-static -C unsafe-allow-abi-mismatch=sanitizer //@ minicore-compile-flags: -Ccodegen-units=1 #![crate_type = "lib"] -#![feature(no_core)] +#![feature(no_core, f16, f128)] #![no_core] extern crate minicore; @@ -154,6 +154,20 @@ pub fn foo59(_: &str, _: &str) {} pub fn foo60(_: &str, _: &str, _: &str) {} // CHECK: define{{.*}}5foo60{{.*}}!type ![[TYPE60:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} +pub fn foo61(_: f16) {} +// CHECK: define{{.*}}5foo61{{.*}}!type ![[TYPE61:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} +pub fn foo62(_: f16, _: f16) {} +// CHECK: define{{.*}}5foo62{{.*}}!type ![[TYPE62:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} +pub fn foo63(_: f16, _: f16, _: f16) {} +// CHECK: define{{.*}}5foo63{{.*}}!type ![[TYPE63:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + +pub fn foo64(_: f128) {} +// CHECK: define{{.*}}5foo64{{.*}}!type ![[TYPE64:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} +pub fn foo65(_: f128, _: f128) {} +// CHECK: define{{.*}}5foo65{{.*}}!type ![[TYPE65:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} +pub fn foo66(_: f128, _: f128, _: f128) {} +// CHECK: define{{.*}}5foo66{{.*}}!type ![[TYPE66:[0-9]+]] !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} + // CHECK: ![[TYPE1]] = !{i64 0, !"_ZTSFvvE"} // CHECK: ![[TYPE4]] = !{i64 0, !"_ZTSFvPvE"} // CHECK: ![[TYPE5]] = !{i64 0, !"_ZTSFvPvS_E"} @@ -212,3 +226,9 @@ pub fn foo60(_: &str, _: &str, _: &str) {} // CHECK: ![[TYPE58]] = !{i64 0, !"_ZTSFvu3refIu3strEE"} // CHECK: ![[TYPE59]] = !{i64 0, !"_ZTSFvu3refIu3strES0_E"} // CHECK: ![[TYPE60]] = !{i64 0, !"_ZTSFvu3refIu3strES0_S0_E"} +// CHECK: ![[TYPE61]] = !{i64 0, !"_ZTSFvDhE"} +// CHECK: ![[TYPE62]] = !{i64 0, !"_ZTSFvDhDhE"} +// CHECK: ![[TYPE63]] = !{i64 0, !"_ZTSFvDhDhDhE"} +// CHECK: ![[TYPE64]] = !{i64 0, !"_ZTSFvgE"} +// CHECK: ![[TYPE65]] = !{i64 0, !"_ZTSFvggE"} +// CHECK: ![[TYPE66]] = !{i64 0, !"_ZTSFvgggE"} From 47fc75df4415debfcd437c61cf80b00d125089ce Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 1 Sep 2026 22:01:02 +0200 Subject: [PATCH 29/39] Revert "retrieve supported GCC targets from the sysroot" This reverts commit 5a5b84aa5fbb5cdef0ecc667fd7afdc876dab08e. --- src/tools/compiletest/src/cli.rs | 9 ------- src/tools/compiletest/src/common.rs | 3 --- src/tools/compiletest/src/directives.rs | 26 +------------------ src/tools/compiletest/src/rustdoc_gui_test.rs | 1 - 4 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/tools/compiletest/src/cli.rs b/src/tools/compiletest/src/cli.rs index 45681eabe03a3..893f5b76724b8 100644 --- a/src/tools/compiletest/src/cli.rs +++ b/src/tools/compiletest/src/cli.rs @@ -401,13 +401,6 @@ pub(crate) fn parse_config(args: Vec) -> Config { let iteration_count = args.iteration_count.unwrap_or(Config::DEFAULT_ITERATION_COUNT); assert!(iteration_count > 0, "`--iteration-count` must be a positive integer"); - let gcc_supported_target_tuples = match default_codegen_backend { - CodegenBackend::Gcc => { - directives::find_gcc_supported_targets(&args.sysroot_base, &args.host) - } - CodegenBackend::Llvm | CodegenBackend::Cranelift => vec![], - }; - // FIXME: this run scheme is... confusing. let run = args.run.and_then(|mode| match mode.as_str() { "auto" => None, @@ -455,8 +448,6 @@ pub(crate) fn parse_config(args: Vec) -> Config { force_pass_mode: args.pass, force_rerun: args.force_rerun, - gcc_supported_target_tuples, - gdb: args.gdb, gdb_version, git_hash: args.git_hash, diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs index 4123dcf5b600a..8c580d3520dfd 100644 --- a/src/tools/compiletest/src/common.rs +++ b/src/tools/compiletest/src/common.rs @@ -770,9 +770,6 @@ pub(crate) struct Config { /// Whether to ignore `//@ ignore-backends`. pub(crate) bypass_ignore_backends: bool, - /// Target tuples for which we've found libgccjit.so. - pub(crate) gcc_supported_target_tuples: Vec, - /// Number of parallel jobs configured for the build. /// /// This is forwarded from bootstrap's `jobs` configuration. diff --git a/src/tools/compiletest/src/directives.rs b/src/tools/compiletest/src/directives.rs index 3499821f7b6e0..3459273c922a1 100644 --- a/src/tools/compiletest/src/directives.rs +++ b/src/tools/compiletest/src/directives.rs @@ -851,30 +851,6 @@ pub(crate) fn extract_llvm_version_from_binary(binary_path: &str) -> Option Vec { - // E.g. `lib/rustlib/x86_64-unknown-linux-gnu/codegen-backends/lib`. - let backends_dir = - sysroot_base.join("lib").join("rustlib").join(host).join("codegen-backends").join("lib"); - - match std::fs::read_dir(&backends_dir) { - Ok(entries) => { - // Search for `aarch64-unknown-linux-gnu/libgccjit.so` et cetera. - let target_tuples: Vec<_> = entries - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.path().join("libgccjit.so").exists()) - .filter_map(|entry| entry.file_name().into_string().ok()) - .collect(); - - if target_tuples.is_empty() { - panic!("did not find `libgccjit.so` for any target in {backends_dir}"); - } - - target_tuples - } - Err(e) => panic!("unable to find `libgccjit.so` for any target in {backends_dir}: {e:?}",), - } -} - /// Takes a directive of the form `" [- ]"`, returns the numeric representation /// of `` and `` as tuple: `(, )`. /// @@ -1255,7 +1231,7 @@ fn ignore_unsupported_backend_target(config: &Config, line: &DirectiveLine<'_>) return IgnoreDecision::Continue; }; - if !config.gcc_supported_target_tuples.iter().any(|t| t == target) { + if target != "x86_64-unknown-linux-gnu" { IgnoreDecision::Ignore { reason: format!( "backend `{}` cannot build for target `{target}`", diff --git a/src/tools/compiletest/src/rustdoc_gui_test.rs b/src/tools/compiletest/src/rustdoc_gui_test.rs index b2d23bcf8ec0a..5a768519f616d 100644 --- a/src/tools/compiletest/src/rustdoc_gui_test.rs +++ b/src/tools/compiletest/src/rustdoc_gui_test.rs @@ -143,7 +143,6 @@ fn incomplete_config_for_rustdoc_gui_test() -> Config { default_codegen_backend: CodegenBackend::Llvm, override_codegen_backend: None, bypass_ignore_backends: Default::default(), - gcc_supported_target_tuples: vec![], jobs: Default::default(), parallel_frontend_threads: Config::DEFAULT_PARALLEL_FRONTEND_THREADS, iteration_count: Config::DEFAULT_ITERATION_COUNT, From b0ba8b100ef633a153432dfe94dcaf11486133ca Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 1 Sep 2026 15:57:03 +1000 Subject: [PATCH 30/39] Introduce `PerOwnerLoweringState` `LoweringContext` has 14 fields that get swapped in and out in `with_hir_id_owner`. This is fragile and gross. This commit moves those fields into a new struct, `PerOwnerLoweringState`, which means they can be swapped in and out cleanly. Other changes: - All `self.foo` accesses to those 14 fields become `self.curr_owner.foo`. - Field renames: - `current_hir_id_owner` -> `owner_id` - `current_disambiguator` -> `disambiguator` - `LoweringContext::make_owner_info` becomes `PerOwnerLoweringState::into_owner_info`; this makes sense because it consumes the `PerOwnerLoweringState`. - Stronger assertions: `into_owner_info` has assertions that now apply to the `with_lctx` path as well as the `with_hir_id_owner` path. --- .../src/delegation/attributes.rs | 6 +- .../src/delegation/generics.rs | 2 +- .../rustc_ast_lowering/src/delegation/mod.rs | 10 +- .../src/delegation/resolution.rs | 4 +- compiler/rustc_ast_lowering/src/expr.rs | 17 +- compiler/rustc_ast_lowering/src/item.rs | 42 +- compiler/rustc_ast_lowering/src/lib.rs | 393 +++++++++--------- compiler/rustc_ast_lowering/src/pat.rs | 8 +- compiler/rustc_ast_lowering/src/path.rs | 2 +- compiler/rustc_middle/src/ty/mod.rs | 7 +- 10 files changed, 241 insertions(+), 250 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/delegation/attributes.rs b/compiler/rustc_ast_lowering/src/delegation/attributes.rs index 885ee0d51c730..834f85450a2cd 100644 --- a/compiler/rustc_ast_lowering/src/delegation/attributes.rs +++ b/compiler/rustc_ast_lowering/src/delegation/attributes.rs @@ -43,17 +43,17 @@ impl<'hir> LoweringContext<'_, 'hir> { let &DelegationResolution { span, sig_id, .. } = resolution; const PARENT_ID: hir::ItemLocalId = hir::ItemLocalId::ZERO; - let new_attrs = self.create_new_attrs(span, sig_id, self.attrs.get(&PARENT_ID)); + let new_attrs = self.create_new_attrs(span, sig_id, self.curr_owner.attrs.get(&PARENT_ID)); if !new_attrs.is_empty() { - let new_attrs = match self.attrs.get(&PARENT_ID) { + let new_attrs = match self.curr_owner.attrs.get(&PARENT_ID) { Some(existing_attrs) => self.arena.alloc_from_iter( existing_attrs.iter().map(|a| a.clone()).chain(new_attrs.into_iter()), ), None => self.arena.alloc_from_iter(new_attrs.into_iter()), }; - self.attrs.insert(PARENT_ID, new_attrs); + self.curr_owner.attrs.insert(PARENT_ID, new_attrs); } } diff --git a/compiler/rustc_ast_lowering/src/delegation/generics.rs b/compiler/rustc_ast_lowering/src/delegation/generics.rs index 911ec5956006d..867ed364433e7 100644 --- a/compiler/rustc_ast_lowering/src/delegation/generics.rs +++ b/compiler/rustc_ast_lowering/src/delegation/generics.rs @@ -587,7 +587,7 @@ impl<'hir> LoweringContext<'_, 'hir> { }; // Important: we don't use `self.next_id()` as we want to execute - // `lower_node_id` routine so param's id is added to `self.children`. + // `lower_node_id` routine so param's id is added to `self.curr_owner.children`. let hir_id = self.lower_node_id(node_id); Some(hir::GenericParam { diff --git a/compiler/rustc_ast_lowering/src/delegation/mod.rs b/compiler/rustc_ast_lowering/src/delegation/mod.rs index 3b9074e67bdd2..a92b62517e61d 100644 --- a/compiler/rustc_ast_lowering/src/delegation/mod.rs +++ b/compiler/rustc_ast_lowering/src/delegation/mod.rs @@ -140,9 +140,11 @@ impl<'hir> LoweringContext<'_, 'hir> { let id = match source { DelegationSource::Single => None, DelegationSource::List(expn_id) => Some(expn_id), - DelegationSource::Glob => { - Some(self.tcx.expn_that_defined(self.owner.def_id).expect_local()) - } + DelegationSource::Glob => Some( + self.tcx + .expn_that_defined(self.curr_owner.owner.def_id) + .expect_local(), + ), }; id.map(|id| (id, unused_target_expr)) @@ -335,7 +337,7 @@ impl<'hir> LoweringContext<'_, 'hir> { let overwrites = self_resolver.overwrites; // Target expr needs to lower `self` path. - self.ident_and_label_to_local_id.insert(pat_node_id, param_local_id); + self.curr_owner.ident_and_label_to_local_id.insert(pat_node_id, param_local_id); let block = cfg_select! { debug_assertions => { diff --git a/compiler/rustc_ast_lowering/src/delegation/resolution.rs b/compiler/rustc_ast_lowering/src/delegation/resolution.rs index dd1b9518e6d7f..85604223c8509 100644 --- a/compiler/rustc_ast_lowering/src/delegation/resolution.rs +++ b/compiler/rustc_ast_lowering/src/delegation/resolution.rs @@ -73,7 +73,7 @@ pub(super) mod resolver { #[inline] pub(crate) fn owner_id(&self) -> LocalDefId { - self.0.owner.def_id + self.0.curr_owner.owner.def_id } /// (from `tests\ui\delegation\target-expr-removal-defs-inside.rs`): @@ -91,7 +91,7 @@ pub(super) mod resolver { #[inline] pub(crate) fn is_definition(&self, id: NodeId) -> bool { self.0.resolver.owners.contains_key(&id) - || self.0.owner.node_id_to_def_id.contains_key(&id) + || self.0.curr_owner.owner.node_id_to_def_id.contains_key(&id) } #[inline] diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 4d5b98fd1ac00..0a4a2ae7145e3 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -172,7 +172,8 @@ impl<'hir> LoweringContext<'_, 'hir> { } // Merge attributes into the inner expression. if !e.attrs.is_empty() { - let old_attrs = self.attrs.get(&ex.hir_id.local_id).copied().unwrap_or(&[]); + let old_attrs = + self.curr_owner.attrs.get(&ex.hir_id.local_id).copied().unwrap_or(&[]); let new_attrs = self .lower_attrs_vec(&e.attrs, e.span, ex.hir_id, Target::from_expr(e)) .into_iter() @@ -181,7 +182,7 @@ impl<'hir> LoweringContext<'_, 'hir> { if new_attrs.is_empty() { return ex; } - self.attrs.insert(ex.hir_id.local_id, new_attrs); + self.curr_owner.attrs.insert(ex.hir_id.local_id, new_attrs); } return ex; } @@ -884,7 +885,7 @@ impl<'hir> LoweringContext<'_, 'hir> { /// `inner_hir_id` in case the `async_fn_track_caller` feature is enabled. pub(super) fn maybe_forward_track_caller(&mut self, outer_hir_id: HirId, inner_hir_id: HirId) { if self.tcx.features().async_fn_track_caller() - && let Some(attrs) = self.attrs.get(&outer_hir_id.local_id) + && let Some(attrs) = self.curr_owner.attrs.get(&outer_hir_id.local_id) && let Some(t) = attrs.iter().find(|a| { matches!( a, @@ -892,7 +893,7 @@ impl<'hir> LoweringContext<'_, 'hir> { ) }) { - self.attrs.insert(inner_hir_id.local_id, std::slice::from_ref(t)); + self.curr_owner.attrs.insert(inner_hir_id.local_id, std::slice::from_ref(t)); } } @@ -1505,16 +1506,16 @@ impl<'hir> LoweringContext<'_, 'hir> { dest_hir_id: hir::HirId, ) -> Option