From fefc6d2b59af5aa4d8c926cfd417654c7840c456 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Wed, 15 Oct 2025 13:29:25 +0000 Subject: [PATCH 01/20] Show backtrace on allocation failures when possible And if an allocation while printing the backtrace fails, don't try to print another backtrace as that will never succeed. --- library/std/src/alloc.rs | 85 ++++++++++++++++--- .../fail/alloc/alloc_error_handler.stderr | 8 +- tests/ui/alloc-error/alloc-error-backtrace.rs | 32 +++++++ .../alloc-error/default-alloc-error-hook.rs | 11 ++- 4 files changed, 116 insertions(+), 20 deletions(-) create mode 100644 tests/ui/alloc-error/alloc-error-backtrace.rs diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index daa25c5a50dd6..c960a0223d383 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -57,7 +57,7 @@ #![stable(feature = "alloc_module", since = "1.28.0")] use core::ptr::NonNull; -use core::sync::atomic::{Atomic, AtomicPtr, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; use core::{hint, mem, ptr}; #[stable(feature = "alloc_module", since = "1.28.0")] @@ -287,7 +287,7 @@ unsafe impl Allocator for System { } } -static HOOK: Atomic<*mut ()> = AtomicPtr::new(ptr::null_mut()); +static HOOK: AtomicPtr<()> = AtomicPtr::new(ptr::null_mut()); /// Registers a custom allocation error hook, replacing any that was previously registered. /// @@ -344,7 +344,12 @@ pub fn take_alloc_error_hook() -> fn(Layout) { if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } } } +#[optimize(size)] fn default_alloc_error_hook(layout: Layout) { + if cfg!(panic = "immediate-abort") { + return; + } + unsafe extern "Rust" { // This symbol is emitted by rustc next to __rust_alloc_error_handler. // Its value depends on the -Zoom={panic,abort} compiler option. @@ -354,16 +359,66 @@ fn default_alloc_error_hook(layout: Layout) { if unsafe { __rust_alloc_error_handler_should_panic_v2() != 0 } { panic!("memory allocation of {} bytes failed", layout.size()); + } + + // This is the default path taken on OOM, and the only path taken on stable with std. + // Crucially, it does *not* call any user-defined code, and therefore users do not have to + // worry about allocation failure causing reentrancy issues. That makes it different from + // the default `__rdl_alloc_error_handler` defined in alloc (i.e., the default alloc error + // handler that is called when there is no `#[alloc_error_handler]`), which triggers a + // regular panic and thus can invoke a user-defined panic hook, executing arbitrary + // user-defined code. + + static PREV_ALLOC_FAILURE: AtomicBool = AtomicBool::new(false); + if PREV_ALLOC_FAILURE.swap(true, Ordering::Relaxed) { + // Don't try to print a backtrace if a previous alloc error happened. This likely means + // there is not enough memory to print a backtrace, although it could also mean that two + // threads concurrently run out of memory. + rtprintpanic!( + "memory allocation of {} bytes failed\nskipping backtrace printing to avoid potential recursion\n", + layout.size() + ); + return; } else { - // This is the default path taken on OOM, and the only path taken on stable with std. - // Crucially, it does *not* call any user-defined code, and therefore users do not have to - // worry about allocation failure causing reentrancy issues. That makes it different from - // the default `__rdl_alloc_error_handler` defined in alloc (i.e., the default alloc error - // handler that is called when there is no `#[alloc_error_handler]`), which triggers a - // regular panic and thus can invoke a user-defined panic hook, executing arbitrary - // user-defined code. rtprintpanic!("memory allocation of {} bytes failed\n", layout.size()); } + + let Some(mut out) = crate::sys::stdio::panic_output() else { + return; + }; + + // Use a lock to prevent mixed output in multithreading context. + // Some platforms also require it when printing a backtrace, like `SymFromAddr` on Windows. + // Make sure to not take this lock until after checking PREV_ALLOC_FAILURE to avoid deadlocks + // when there is too little memory to print a backtrace. + let mut lock = crate::sys::backtrace::lock(); + + match crate::panic::get_backtrace_style() { + // SAFETY: we took out a lock just a second ago. + Some(crate::panic::BacktraceStyle::Short) => { + drop(lock.print(&mut out, crate::backtrace_rs::PrintFmt::Short)) + } + Some(crate::panic::BacktraceStyle::Full) => { + drop(lock.print(&mut out, crate::backtrace_rs::PrintFmt::Full)) + } + Some(crate::panic::BacktraceStyle::Off) => { + use crate::io::Write; + let _ = writeln!( + out, + "note: run with `RUST_BACKTRACE=1` environment variable to display a \ + backtrace" + ); + if cfg!(miri) { + let _ = writeln!( + out, + "note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` \ + for the environment variable to have an effect" + ); + } + } + // If backtraces aren't supported or are forced-off, do nothing. + None => {} + } } #[cfg(not(test))] @@ -371,11 +426,13 @@ fn default_alloc_error_hook(layout: Layout) { #[alloc_error_handler] #[unstable(feature = "alloc_internals", issue = "none")] pub fn rust_oom(layout: Layout) -> ! { - let hook = HOOK.load(Ordering::Acquire); - let hook: fn(Layout) = - if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } }; - hook(layout); - crate::process::abort() + crate::sys::backtrace::__rust_end_short_backtrace(|| { + let hook = HOOK.load(Ordering::Acquire); + let hook: fn(Layout) = + if hook.is_null() { default_alloc_error_hook } else { unsafe { mem::transmute(hook) } }; + hook(layout); + crate::process::abort() + }) } #[cfg(not(test))] diff --git a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr index 7f7fc63ac0ddd..b85a23e3c7db1 100644 --- a/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr +++ b/src/tools/miri/tests/fail/alloc/alloc_error_handler.stderr @@ -1,11 +1,15 @@ memory allocation of 4 bytes failed +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +note: in Miri, you may have to set `MIRIFLAGS=-Zmiri-env-forward=RUST_BACKTRACE` for the environment variable to have an effect error: abnormal termination: the program aborted execution --> RUSTLIB/std/src/alloc.rs:LL:CC | -LL | crate::process::abort() - | ^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here +LL | crate::process::abort() + | ^^^^^^^^^^^^^^^^^^^^^^^ abnormal termination occurred here | = note: BACKTRACE: + = note: inside closure at RUSTLIB/std/src/alloc.rs:LL:CC + = note: inside `std::sys::backtrace::__rust_end_short_backtrace::<{closure@std::alloc::rust_oom::{closure#0}}, !>` at RUSTLIB/std/src/sys/backtrace.rs:LL:CC = note: inside `std::alloc::rust_oom` at RUSTLIB/std/src/alloc.rs:LL:CC = note: inside `std::alloc::_::__rust_alloc_error_handler` at RUSTLIB/std/src/alloc.rs:LL:CC = note: inside `std::alloc::handle_alloc_error::rt_error` at RUSTLIB/alloc/src/alloc.rs:LL:CC diff --git a/tests/ui/alloc-error/alloc-error-backtrace.rs b/tests/ui/alloc-error/alloc-error-backtrace.rs new file mode 100644 index 0000000000000..4185586ba0448 --- /dev/null +++ b/tests/ui/alloc-error/alloc-error-backtrace.rs @@ -0,0 +1,32 @@ +//@ run-pass +//@ ignore-android FIXME #17520 +//@ needs-subprocess +//@ ignore-openbsd no support for libbacktrace without filename +//@ ignore-fuchsia Backtraces not symbolized +//@ compile-flags:-g +//@ compile-flags:-Cstrip=none + +use std::alloc::{Layout, handle_alloc_error}; +use std::process::Command; +use std::{env, str}; + +fn main() { + if env::args().len() > 1 { + handle_alloc_error(Layout::new::<[u8; 42]>()) + } + + let me = env::current_exe().unwrap(); + let output = Command::new(&me).env("RUST_BACKTRACE", "1").arg("next").output().unwrap(); + assert!(!output.status.success(), "{:?} is a success", output.status); + + let mut stderr = str::from_utf8(&output.stderr).unwrap(); + + // When running inside QEMU user-mode emulation, there will be an extra message printed by QEMU + // in the stderr whenever a core dump happens. Remove it before the check. + stderr = stderr + .strip_suffix("qemu: uncaught target signal 6 (Aborted) - core dumped\n") + .unwrap_or(stderr); + + assert!(stderr.contains("memory allocation of 42 bytes failed"), "{}", stderr); + assert!(stderr.contains("alloc_error_backtrace::main"), "{}", stderr); +} diff --git a/tests/ui/alloc-error/default-alloc-error-hook.rs b/tests/ui/alloc-error/default-alloc-error-hook.rs index 7fbc66ca5f472..3092f5044c080 100644 --- a/tests/ui/alloc-error/default-alloc-error-hook.rs +++ b/tests/ui/alloc-error/default-alloc-error-hook.rs @@ -2,9 +2,8 @@ //@ needs-subprocess use std::alloc::{Layout, handle_alloc_error}; -use std::env; use std::process::Command; -use std::str; +use std::{env, str}; fn main() { if env::args().len() > 1 { @@ -12,7 +11,7 @@ fn main() { } let me = env::current_exe().unwrap(); - let output = Command::new(&me).arg("next").output().unwrap(); + let output = Command::new(&me).env("RUST_BACKTRACE", "0").arg("next").output().unwrap(); assert!(!output.status.success(), "{:?} is a success", output.status); let mut stderr = str::from_utf8(&output.stderr).unwrap(); @@ -23,5 +22,9 @@ fn main() { .strip_suffix("qemu: uncaught target signal 6 (Aborted) - core dumped\n") .unwrap_or(stderr); - assert_eq!(stderr, "memory allocation of 42 bytes failed\n"); + assert_eq!( + stderr, + "memory allocation of 42 bytes failed\n\ + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + ); } From 1f23b4849272186b9294347c8268571e07fdcb49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Wed, 29 Oct 2025 02:49:16 +0100 Subject: [PATCH 02/20] rustdoc: Properly highlight shebang and frontmatter --- src/librustdoc/html/highlight.rs | 200 ++++++++++-------- tests/rustdoc/jump-to-def/shebang.rs | 15 ++ .../rustdoc/source-code-pages/frontmatter.rs | 10 + tests/rustdoc/source-code-pages/shebang.rs | 6 + 4 files changed, 137 insertions(+), 94 deletions(-) create mode 100644 tests/rustdoc/jump-to-def/shebang.rs create mode 100644 tests/rustdoc/source-code-pages/frontmatter.rs create mode 100644 tests/rustdoc/source-code-pages/shebang.rs diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index c37736f137df9..81dde140fa819 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -566,52 +566,52 @@ pub(super) fn write_code( }; let mut current_expansion = get_expansion(&mut token_handler, expanded_codes, file_span); - Classifier::new( + classify( &src, - token_handler.href_context.as_ref().map(|c| c.file_span).unwrap_or(DUMMY_SP), + token_handler.href_context.as_ref().map_or(DUMMY_SP, |c| c.file_span), decoration_info, - ) - .highlight(&mut |span, highlight| match highlight { - Highlight::Token { text, class } => { - token_handler.push_token(class, Cow::Borrowed(text)); - - if text == "\n" { - if current_expansion.is_none() { - current_expansion = get_expansion(&mut token_handler, expanded_codes, span); - } - if let Some(ref current_expansion) = current_expansion - && current_expansion.span.lo() == span.hi() - { - token_handler.add_expanded_code(current_expansion); - } - } else { - let mut need_end = false; - if let Some(ref current_expansion) = current_expansion { - if current_expansion.span.lo() == span.hi() { - token_handler.add_expanded_code(current_expansion); - } else if current_expansion.end_line == token_handler.line - && span.hi() >= current_expansion.span.hi() + &mut |span, highlight| match highlight { + Highlight::Token { text, class } => { + token_handler.push_token(class, Cow::Borrowed(text)); + + if text == "\n" { + if current_expansion.is_none() { + current_expansion = get_expansion(&mut token_handler, expanded_codes, span); + } + if let Some(ref current_expansion) = current_expansion + && current_expansion.span.lo() == span.hi() { - need_end = true; + token_handler.add_expanded_code(current_expansion); + } + } else { + let mut need_end = false; + if let Some(ref current_expansion) = current_expansion { + if current_expansion.span.lo() == span.hi() { + token_handler.add_expanded_code(current_expansion); + } else if current_expansion.end_line == token_handler.line + && span.hi() >= current_expansion.span.hi() + { + need_end = true; + } + } + if need_end { + current_expansion = end_expansion(&mut token_handler, expanded_codes, span); } - } - if need_end { - current_expansion = end_expansion(&mut token_handler, expanded_codes, span); } } - } - Highlight::EnterSpan { class } => { - token_handler.class_stack.enter_elem( - token_handler.out, - &token_handler.href_context, - class, - None, - ); - } - Highlight::ExitSpan => { - token_handler.class_stack.exit_elem(); - } - }); + Highlight::EnterSpan { class } => { + token_handler.class_stack.enter_elem( + token_handler.out, + &token_handler.href_context, + class, + None, + ); + } + Highlight::ExitSpan => { + token_handler.class_stack.exit_elem(); + } + }, + ); } fn write_footer(playground_button: Option<&str>) -> impl Display { @@ -735,6 +735,12 @@ struct TokenIter<'a> { cursor: Cursor<'a>, } +impl<'a> TokenIter<'a> { + fn new(src: &'a str) -> Self { + Self { src, cursor: Cursor::new(src, FrontmatterAllowed::Yes) } + } +} + impl<'a> Iterator for TokenIter<'a> { type Item = (TokenKind, &'a str); fn next(&mut self) -> Option<(TokenKind, &'a str)> { @@ -843,6 +849,54 @@ fn new_span(lo: u32, text: &str, file_span: Span) -> Span { file_span.with_lo(file_lo + BytePos(lo)).with_hi(file_lo + BytePos(hi)) } +fn classify<'src>( + src: &'src str, + file_span: Span, + decoration_info: Option<&DecorationInfo>, + sink: &mut dyn FnMut(Span, Highlight<'src>), +) { + let offset = rustc_lexer::strip_shebang(src); + + if let Some(offset) = offset { + sink(DUMMY_SP, Highlight::Token { text: &src[..offset], class: Some(Class::Comment) }); + } + + let mut classifier = + Classifier::new(src, offset.unwrap_or_default(), file_span, decoration_info); + + loop { + if let Some(decs) = classifier.decorations.as_mut() { + let byte_pos = classifier.byte_pos; + let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count(); + for (_, kind) in decs.starts.drain(0..n_starts) { + sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Decoration(kind) }); + } + + let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count(); + for _ in decs.ends.drain(0..n_ends) { + sink(DUMMY_SP, Highlight::ExitSpan); + } + } + + if let Some((TokenKind::Colon | TokenKind::Ident, _)) = classifier.tokens.peek() { + let tokens = classifier.get_full_ident_path(); + for &(token, start, end) in &tokens { + let text = &classifier.src[start..end]; + classifier.advance(token, text, sink, start as u32); + classifier.byte_pos += text.len() as u32; + } + if !tokens.is_empty() { + continue; + } + } + if let Some((token, text, before)) = classifier.next() { + classifier.advance(token, text, sink, before); + } else { + break; + } + } +} + /// Processes program tokens, classifying strings of text by highlighting /// category (`Class`). struct Classifier<'src> { @@ -857,21 +911,23 @@ struct Classifier<'src> { } impl<'src> Classifier<'src> { - /// Takes as argument the source code to HTML-ify, the rust edition to use and the source code - /// file span which will be used later on by the `span_correspondence_map`. - fn new(src: &'src str, file_span: Span, decoration_info: Option<&DecorationInfo>) -> Self { - let tokens = - PeekIter::new(TokenIter { src, cursor: Cursor::new(src, FrontmatterAllowed::Yes) }); - let decorations = decoration_info.map(Decorations::new); + /// Takes as argument the source code to HTML-ify and the source code file span + /// which will be used later on by the `span_correspondence_map`. + fn new( + src: &'src str, + byte_pos: usize, + file_span: Span, + decoration_info: Option<&DecorationInfo>, + ) -> Self { Classifier { - tokens, + tokens: PeekIter::new(TokenIter::new(&src[byte_pos..])), in_attribute: false, in_macro: false, in_macro_nonterminal: false, - byte_pos: 0, + byte_pos: byte_pos as u32, file_span, src, - decorations, + decorations: decoration_info.map(Decorations::new), } } @@ -938,50 +994,6 @@ impl<'src> Classifier<'src> { } } - /// Exhausts the `Classifier` writing the output into `sink`. - /// - /// The general structure for this method is to iterate over each token, - /// possibly giving it an HTML span with a class specifying what flavor of - /// token is used. - fn highlight(mut self, sink: &mut dyn FnMut(Span, Highlight<'src>)) { - loop { - if let Some(decs) = self.decorations.as_mut() { - let byte_pos = self.byte_pos; - let n_starts = decs.starts.iter().filter(|(i, _)| byte_pos >= *i).count(); - for (_, kind) in decs.starts.drain(0..n_starts) { - sink(DUMMY_SP, Highlight::EnterSpan { class: Class::Decoration(kind) }); - } - - let n_ends = decs.ends.iter().filter(|i| byte_pos >= **i).count(); - for _ in decs.ends.drain(0..n_ends) { - sink(DUMMY_SP, Highlight::ExitSpan); - } - } - - if self - .tokens - .peek() - .map(|t| matches!(t.0, TokenKind::Colon | TokenKind::Ident)) - .unwrap_or(false) - { - let tokens = self.get_full_ident_path(); - for (token, start, end) in &tokens { - let text = &self.src[*start..*end]; - self.advance(*token, text, sink, *start as u32); - self.byte_pos += text.len() as u32; - } - if !tokens.is_empty() { - continue; - } - } - if let Some((token, text, before)) = self.next() { - self.advance(token, text, sink, before); - } else { - break; - } - } - } - /// Single step of highlighting. This will classify `token`, but maybe also a couple of /// following ones as well. /// @@ -1019,6 +1031,7 @@ impl<'src> Classifier<'src> { Class::Comment } } + TokenKind::Frontmatter { .. } => Class::Comment, // Consider this as part of a macro invocation if there was a // leading identifier. TokenKind::Bang if self.in_macro => { @@ -1117,7 +1130,6 @@ impl<'src> Classifier<'src> { | TokenKind::At | TokenKind::Tilde | TokenKind::Colon - | TokenKind::Frontmatter { .. } | TokenKind::Unknown => return no_highlight(sink), TokenKind::Question => Class::QuestionMark, diff --git a/tests/rustdoc/jump-to-def/shebang.rs b/tests/rustdoc/jump-to-def/shebang.rs new file mode 100644 index 0000000000000..a631762554b1a --- /dev/null +++ b/tests/rustdoc/jump-to-def/shebang.rs @@ -0,0 +1,15 @@ +#!/path/to/my/interpreter +//@ compile-flags: -Zunstable-options --generate-link-to-definition + +// Ensure that we can successfully generate links to definitions in the presence of shebang. +// Implementation-wise, shebang is not a token that's emitted by the lexer. Instead, we need +// to offset the actual lexing which is tricky due to all the byte index and span calculations +// in the Classifier. + +fn scope() { +//@ has 'src/shebang/shebang.rs.html' +//@ has - '//a[@href="#15"]' 'function' + function(); +} + +fn function() {} diff --git a/tests/rustdoc/source-code-pages/frontmatter.rs b/tests/rustdoc/source-code-pages/frontmatter.rs new file mode 100644 index 0000000000000..c352504bab2b2 --- /dev/null +++ b/tests/rustdoc/source-code-pages/frontmatter.rs @@ -0,0 +1,10 @@ +--- json +{"edition": "2024"} +--- +#![feature(frontmatter)] + +// Test that we highlight frontmatter as comments on source code pages. + +//@ has 'src/frontmatter/frontmatter.rs.html' +//@ has - '//pre[@class="rust"]//span[@class="comment"]' \ +// '--- json {"edition": "2024"} ---' diff --git a/tests/rustdoc/source-code-pages/shebang.rs b/tests/rustdoc/source-code-pages/shebang.rs new file mode 100644 index 0000000000000..975ca3a31858b --- /dev/null +++ b/tests/rustdoc/source-code-pages/shebang.rs @@ -0,0 +1,6 @@ +#!/path/to/somewhere 0 if false "" + +// Test that we highlight shebang as comments on source code pages. + +//@ has 'src/shebang/shebang.rs.html' +//@ has - '//pre[@class="rust"]//span[@class="comment"]' '#!/path/to/somewhere 0 if false ""' From a5f07138ac19744fc283c07e278d21b61360e43e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 28 Oct 2025 21:21:54 +0100 Subject: [PATCH 03/20] rustdoc: Recognize more weak keywords when highlighting Rust code --- src/librustdoc/html/highlight.rs | 78 ++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 81dde140fa819..7c22a7ab91f66 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -764,7 +764,8 @@ fn get_real_ident_class(text: &str, allow_path_keywords: bool) -> Option Some(match text { "ref" | "mut" => Class::RefKeyWord, "false" | "true" => Class::Bool, - _ if Symbol::intern(text).is_reserved(|| Edition::Edition2021) => Class::KeyWord, + // FIXME(#148221): Don't hard-code the edition. The classifier should take it as an argument. + _ if Symbol::intern(text).is_reserved(|| Edition::Edition2024) => Class::KeyWord, _ => return None, }) } @@ -1201,7 +1202,7 @@ impl<'src> Classifier<'src> { }, TokenKind::GuardedStrPrefix => return no_highlight(sink), TokenKind::Ident | TokenKind::RawIdent - if self.peek_non_whitespace() == Some(TokenKind::Bang) => + if let Some((TokenKind::Bang, _)) = self.peek_non_trivia() => { self.in_macro = true; let span = new_span(before, text, file_span); @@ -1209,26 +1210,22 @@ impl<'src> Classifier<'src> { sink(span, Highlight::Token { text, class: None }); return; } - TokenKind::Ident => { - match get_real_ident_class(text, false) { - None => match text { - "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), - "Some" | "None" | "Ok" | "Err" => { - Class::PreludeVal(new_span(before, text, file_span)) - } - // "union" is a weak keyword and is only considered as a keyword when declaring - // a union type. - "union" if self.check_if_is_union_keyword() => Class::KeyWord, - _ if self.in_macro_nonterminal => { - self.in_macro_nonterminal = false; - Class::MacroNonTerminal - } - "self" | "Self" => Class::Self_(new_span(before, text, file_span)), - _ => Class::Ident(new_span(before, text, file_span)), - }, - Some(c) => c, - } - } + TokenKind::Ident => match get_real_ident_class(text, false) { + None => match text { + "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), + "Some" | "None" | "Ok" | "Err" => { + Class::PreludeVal(new_span(before, text, file_span)) + } + _ if self.is_weak_keyword(text) => Class::KeyWord, + _ if self.in_macro_nonterminal => { + self.in_macro_nonterminal = false; + Class::MacroNonTerminal + } + "self" | "Self" => Class::Self_(new_span(before, text, file_span)), + _ => Class::Ident(new_span(before, text, file_span)), + }, + Some(c) => c, + }, TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => { Class::Ident(new_span(before, text, file_span)) } @@ -1249,25 +1246,40 @@ impl<'src> Classifier<'src> { } } + fn is_weak_keyword(&mut self, text: &str) -> bool { + // NOTE: `yeet` (`do yeet $expr`), `catch` (`do catch $block`), `default` (specialization), + // `contract_{ensures,requires}`, `builtin` (builtin_syntax) & `reuse` (fn_delegation) are + // too difficult or annoying to properly detect under this simple scheme. + + let matches = match text { + "auto" => |text| text == "trait", // `auto trait Trait {}` (`auto_traits`) + "pin" => |text| text == "const" || text == "mut", // `&pin mut Type` (`pin_ergonomics`) + "raw" => |text| text == "const" || text == "mut", // `&raw const local` + "safe" => |text| text == "fn" || text == "extern", // `unsafe extern { safe fn f(); }` + "union" => |_| true, // `union Untagged { field: () }` + _ => return false, + }; + matches!(self.peek_non_trivia(), Some((TokenKind::Ident, text)) if matches(text)) + } + fn peek(&mut self) -> Option { - self.tokens.peek().map(|(token_kind, _text)| *token_kind) + self.tokens.peek().map(|&(kind, _)| kind) } - fn peek_non_whitespace(&mut self) -> Option { - while let Some((token_kind, _)) = self.tokens.peek_next() { - if *token_kind != TokenKind::Whitespace { - let token_kind = *token_kind; - self.tokens.stop_peeking(); - return Some(token_kind); + fn peek_non_trivia(&mut self) -> Option<(TokenKind, &str)> { + while let Some(&token @ (kind, _)) = self.tokens.peek_next() { + if let TokenKind::Whitespace + | TokenKind::LineComment { doc_style: None } + | TokenKind::BlockComment { doc_style: None, .. } = kind + { + continue; } + self.tokens.stop_peeking(); + return Some(token); } self.tokens.stop_peeking(); None } - - fn check_if_is_union_keyword(&mut self) -> bool { - self.peek_non_whitespace().is_some_and(|kind| kind == TokenKind::Ident) - } } fn generate_link_to_def( From 0582085e6fac50865a0d7217d74ef9260f05a6ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Tue, 28 Oct 2025 22:30:58 +0100 Subject: [PATCH 04/20] rustdoc: Refactor keyword highlighting and make metavars take precedence --- src/librustdoc/html/highlight.rs | 83 ++++++++++++++------------------ 1 file changed, 37 insertions(+), 46 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 7c22a7ab91f66..bc872573cc184 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -754,22 +754,6 @@ impl<'a> Iterator for TokenIter<'a> { } } -/// Classifies into identifier class; returns `None` if this is a non-keyword identifier. -fn get_real_ident_class(text: &str, allow_path_keywords: bool) -> Option { - let ignore: &[&str] = - if allow_path_keywords { &["self", "Self", "super", "crate"] } else { &["self", "Self"] }; - if ignore.contains(&text) { - return None; - } - Some(match text { - "ref" | "mut" => Class::RefKeyWord, - "false" | "true" => Class::Bool, - // FIXME(#148221): Don't hard-code the edition. The classifier should take it as an argument. - _ if Symbol::intern(text).is_reserved(|| Edition::Edition2024) => Class::KeyWord, - _ => return None, - }) -} - /// This iterator comes from the same idea than "Peekable" except that it allows to "peek" more than /// just the next item by using `peek_next`. The `peek` method always returns the next item after /// the current one whereas `peek_next` will return the next item after the last one peeked. @@ -787,16 +771,16 @@ impl<'a> PeekIter<'a> { Self { stored: VecDeque::new(), peek_pos: 0, iter } } /// Returns the next item after the current one. It doesn't interfere with `peek_next` output. - fn peek(&mut self) -> Option<&(TokenKind, &'a str)> { + fn peek(&mut self) -> Option<(TokenKind, &'a str)> { if self.stored.is_empty() && let Some(next) = self.iter.next() { self.stored.push_back(next); } - self.stored.front() + self.stored.front().copied() } /// Returns the next item after the last one peeked. It doesn't interfere with `peek` output. - fn peek_next(&mut self) -> Option<&(TokenKind, &'a str)> { + fn peek_next(&mut self) -> Option<(TokenKind, &'a str)> { self.peek_pos += 1; if self.peek_pos - 1 < self.stored.len() { self.stored.get(self.peek_pos - 1) @@ -806,6 +790,7 @@ impl<'a> PeekIter<'a> { } else { None } + .copied() } fn stop_peeking(&mut self) { @@ -956,15 +941,10 @@ impl<'src> Classifier<'src> { } } - if let Some((None, text)) = self.tokens.peek().map(|(token, text)| { - if *token == TokenKind::Ident { - let class = get_real_ident_class(text, true); - (class, text) - } else { - // Doesn't matter which Class we put in here... - (Some(Class::Comment), text) - } - }) { + if let Some((TokenKind::Ident, text)) = self.tokens.peek() + && let symbol = Symbol::intern(text) + && (symbol.is_path_segment_keyword() || !is_keyword(symbol)) + { // We only "add" the colon if there is an ident behind. pos += text.len() + nb; has_ident = true; @@ -1210,22 +1190,7 @@ impl<'src> Classifier<'src> { sink(span, Highlight::Token { text, class: None }); return; } - TokenKind::Ident => match get_real_ident_class(text, false) { - None => match text { - "Option" | "Result" => Class::PreludeTy(new_span(before, text, file_span)), - "Some" | "None" | "Ok" | "Err" => { - Class::PreludeVal(new_span(before, text, file_span)) - } - _ if self.is_weak_keyword(text) => Class::KeyWord, - _ if self.in_macro_nonterminal => { - self.in_macro_nonterminal = false; - Class::MacroNonTerminal - } - "self" | "Self" => Class::Self_(new_span(before, text, file_span)), - _ => Class::Ident(new_span(before, text, file_span)), - }, - Some(c) => c, - }, + TokenKind::Ident => self.classify_ident(before, text), TokenKind::RawIdent | TokenKind::UnknownPrefix | TokenKind::InvalidIdent => { Class::Ident(new_span(before, text, file_span)) } @@ -1246,6 +1211,27 @@ impl<'src> Classifier<'src> { } } + fn classify_ident(&mut self, before: u32, text: &'src str) -> Class { + // Macro non-terminals (meta vars) take precedence. + if self.in_macro_nonterminal { + self.in_macro_nonterminal = false; + return Class::MacroNonTerminal; + } + + let file_span = self.file_span; + let span = || new_span(before, text, file_span); + + match text { + "ref" | "mut" => Class::RefKeyWord, + "false" | "true" => Class::Bool, + "self" | "Self" => Class::Self_(span()), + "Option" | "Result" => Class::PreludeTy(span()), + "Some" | "None" | "Ok" | "Err" => Class::PreludeVal(span()), + _ if self.is_weak_keyword(text) || is_keyword(Symbol::intern(text)) => Class::KeyWord, + _ => Class::Ident(span()), + } + } + fn is_weak_keyword(&mut self, text: &str) -> bool { // NOTE: `yeet` (`do yeet $expr`), `catch` (`do catch $block`), `default` (specialization), // `contract_{ensures,requires}`, `builtin` (builtin_syntax) & `reuse` (fn_delegation) are @@ -1263,11 +1249,11 @@ impl<'src> Classifier<'src> { } fn peek(&mut self) -> Option { - self.tokens.peek().map(|&(kind, _)| kind) + self.tokens.peek().map(|(kind, _)| kind) } fn peek_non_trivia(&mut self) -> Option<(TokenKind, &str)> { - while let Some(&token @ (kind, _)) = self.tokens.peek_next() { + while let Some(token @ (kind, _)) = self.tokens.peek_next() { if let TokenKind::Whitespace | TokenKind::LineComment { doc_style: None } | TokenKind::BlockComment { doc_style: None, .. } = kind @@ -1282,6 +1268,11 @@ impl<'src> Classifier<'src> { } } +fn is_keyword(symbol: Symbol) -> bool { + // FIXME(#148221): Don't hard-code the edition. The classifier should take it as an argument. + symbol.is_reserved(|| Edition::Edition2024) +} + fn generate_link_to_def( out: &mut impl Write, text_s: &str, From f3fe6bad2fceceabfc309f981f36ee21d8896e79 Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Wed, 5 Nov 2025 18:10:07 -0800 Subject: [PATCH 05/20] Fix rust-by-example spanish translation A spanish translation was added in https://github.com/rust-lang/rust-by-example/pull/1910, but the upstream integration was never added. --- src/bootstrap/src/core/build_steps/doc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 6622aae069d5c..020ee8527e992 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -73,7 +73,7 @@ book!( EditionGuide, "src/doc/edition-guide", "edition-guide", &[]; EmbeddedBook, "src/doc/embedded-book", "embedded-book", &[]; Nomicon, "src/doc/nomicon", "nomicon", &[]; - RustByExample, "src/doc/rust-by-example", "rust-by-example", &["ja", "zh"]; + RustByExample, "src/doc/rust-by-example", "rust-by-example", &["es", "ja", "zh"]; RustdocBook, "src/doc/rustdoc", "rustdoc", &[]; StyleGuide, "src/doc/style-guide", "style-guide", &[]; ); From 91ed7a6f6821de4934541dc5a49ee63c0bf4a707 Mon Sep 17 00:00:00 2001 From: bjorn3 <17426603+bjorn3@users.noreply.github.com> Date: Thu, 6 Nov 2025 10:26:34 +0000 Subject: [PATCH 06/20] Move and update safety comment --- library/std/src/alloc.rs | 1 - library/std/src/panicking.rs | 1 - library/std/src/sys/backtrace.rs | 6 ++++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/std/src/alloc.rs b/library/std/src/alloc.rs index c960a0223d383..701b3be6894da 100644 --- a/library/std/src/alloc.rs +++ b/library/std/src/alloc.rs @@ -394,7 +394,6 @@ fn default_alloc_error_hook(layout: Layout) { let mut lock = crate::sys::backtrace::lock(); match crate::panic::get_backtrace_style() { - // SAFETY: we took out a lock just a second ago. Some(crate::panic::BacktraceStyle::Short) => { drop(lock.print(&mut out, crate::backtrace_rs::PrintFmt::Short)) } diff --git a/library/std/src/panicking.rs b/library/std/src/panicking.rs index b7be869c4eb48..76b5d6afa278c 100644 --- a/library/std/src/panicking.rs +++ b/library/std/src/panicking.rs @@ -293,7 +293,6 @@ fn default_hook(info: &PanicHookInfo<'_>) { static FIRST_PANIC: Atomic = AtomicBool::new(true); match backtrace { - // SAFETY: we took out a lock just a second ago. Some(BacktraceStyle::Short) => { drop(lock.print(err, crate::backtrace_rs::PrintFmt::Short)) } diff --git a/library/std/src/sys/backtrace.rs b/library/std/src/sys/backtrace.rs index 57682207e078e..8e4e6aab0e49a 100644 --- a/library/std/src/sys/backtrace.rs +++ b/library/std/src/sys/backtrace.rs @@ -20,8 +20,6 @@ pub(crate) fn lock<'a>() -> BacktraceLock<'a> { impl BacktraceLock<'_> { /// Prints the current backtrace. - /// - /// NOTE: this function is not Sync. The caller must hold a mutex lock, or there must be only one thread in the program. pub(crate) fn print(&mut self, w: &mut dyn Write, format: PrintFmt) -> io::Result<()> { // There are issues currently linking libbacktrace into tests, and in // general during std's own unit tests we're not testing this path. In @@ -36,6 +34,7 @@ impl BacktraceLock<'_> { } impl fmt::Display for DisplayBacktrace { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + // SAFETY: the backtrace lock is held unsafe { _print_fmt(fmt, self.format) } } } @@ -43,6 +42,9 @@ impl BacktraceLock<'_> { } } +/// # Safety +/// +/// This function is not Sync. The caller must hold a mutex lock, or there must be only one thread in the program. unsafe fn _print_fmt(fmt: &mut fmt::Formatter<'_>, print_fmt: PrintFmt) -> fmt::Result { // Always 'fail' to get the cwd when running under Miri - // this allows Miri to display backtraces in isolation mode From 1a9cc7857760043487f00c86690938d0638dd91a Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 6 Nov 2025 19:45:44 +0100 Subject: [PATCH 07/20] re-use `self.get_all_attrs` result for pass indirectly attribute --- compiler/rustc_middle/src/ty/mod.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index d253deb2fe8fd..faa8420c20d0e 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1512,9 +1512,8 @@ impl<'tcx> TyCtxt<'tcx> { field_shuffle_seed ^= user_seed; } - if let Some(reprs) = - find_attr!(self.get_all_attrs(did), AttributeKind::Repr { reprs, .. } => reprs) - { + let attributes = self.get_all_attrs(did); + if let Some(reprs) = find_attr!(attributes, AttributeKind::Repr { reprs, .. } => reprs) { for (r, _) in reprs { flags.insert(match *r { attr::ReprRust => ReprFlags::empty(), @@ -1574,10 +1573,7 @@ impl<'tcx> TyCtxt<'tcx> { } // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. - if find_attr!( - self.get_all_attrs(did), - AttributeKind::RustcPassIndirectlyInNonRusticAbis(..) - ) { + if find_attr!(attributes, AttributeKind::RustcPassIndirectlyInNonRusticAbis(..)) { flags.insert(ReprFlags::PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS); } From 12cde3091a5d258c2f3e140e47537818680cb58d Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 7 Nov 2025 08:27:23 +0800 Subject: [PATCH 08/20] Add note for identifier with attempted hygiene violation --- .../rustc_resolve/src/late/diagnostics.rs | 24 +++++++++++++++++++ .../macros/macro-hygiene-help-issue-148580.rs | 15 ++++++++++++ .../macro-hygiene-help-issue-148580.stderr | 19 +++++++++++++++ .../macros/macro-hygiene-scope-15167.stderr | 20 ++++++++++++++++ .../metavar-expressions/concat-hygiene.stderr | 5 ++++ .../proc-macro/gen-macro-rules-hygiene.stderr | 5 ++++ tests/ui/proc-macro/mixed-site-span.stderr | 5 ++++ tests/ui/proc-macro/weird-hygiene.stderr | 10 ++++++++ 8 files changed, 103 insertions(+) create mode 100644 tests/ui/macros/macro-hygiene-help-issue-148580.rs create mode 100644 tests/ui/macros/macro-hygiene-help-issue-148580.stderr diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 1d00a6b81fabc..ad3493d93e80a 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1125,6 +1125,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } } + + self.suggest_ident_hidden_by_hygiene(err, path, span); } else if err_code == E0412 { if let Some(correct) = Self::likely_rust_type(path) { err.span_suggestion( @@ -1138,6 +1140,28 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> { } } + fn suggest_ident_hidden_by_hygiene(&self, err: &mut Diag<'_>, path: &[Segment], span: Span) { + let [segment] = path else { return }; + + let ident = segment.ident; + let callsite_span = span.source_callsite(); + for rib in self.ribs[ValueNS].iter().rev() { + for (binding_ident, _) in &rib.bindings { + if binding_ident.name == ident.name + && !binding_ident.span.eq_ctxt(span) + && !binding_ident.span.from_expansion() + && binding_ident.span.lo() < callsite_span.lo() + { + err.span_help( + binding_ident.span, + "an identifier with the same name exists, but is not accessible due to macro hygiene", + ); + return; + } + } + } + } + /// Emit special messages for unresolved `Self` and `self`. fn suggest_self_ty( &self, diff --git a/tests/ui/macros/macro-hygiene-help-issue-148580.rs b/tests/ui/macros/macro-hygiene-help-issue-148580.rs new file mode 100644 index 0000000000000..8441290b17228 --- /dev/null +++ b/tests/ui/macros/macro-hygiene-help-issue-148580.rs @@ -0,0 +1,15 @@ +macro_rules! print_it { {} => { println!("{:?}", it); } } +//~^ ERROR cannot find value `it` in this scope + +fn main() { + { + let it = "hello"; + } + { + let it = "world"; + { + let it = (); + print_it!(); + } + } +} diff --git a/tests/ui/macros/macro-hygiene-help-issue-148580.stderr b/tests/ui/macros/macro-hygiene-help-issue-148580.stderr new file mode 100644 index 0000000000000..f6a4ae7dd1c66 --- /dev/null +++ b/tests/ui/macros/macro-hygiene-help-issue-148580.stderr @@ -0,0 +1,19 @@ +error[E0425]: cannot find value `it` in this scope + --> $DIR/macro-hygiene-help-issue-148580.rs:1:50 + | +LL | macro_rules! print_it { {} => { println!("{:?}", it); } } + | ^^ not found in this scope +... +LL | print_it!(); + | ----------- in this macro invocation + | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-help-issue-148580.rs:11:17 + | +LL | let it = (); + | ^^ + = note: this error originates in the macro `print_it` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/tests/ui/macros/macro-hygiene-scope-15167.stderr b/tests/ui/macros/macro-hygiene-scope-15167.stderr index 58112c52df159..332a58467cee1 100644 --- a/tests/ui/macros/macro-hygiene-scope-15167.stderr +++ b/tests/ui/macros/macro-hygiene-scope-15167.stderr @@ -7,6 +7,11 @@ LL | macro_rules! f { () => (n) } LL | println!("{}", f!()); | ---- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-scope-15167.rs:12:9 + | +LL | for n in 0..1 { + | ^ = note: this error originates in the macro `f` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `n` in this scope @@ -18,6 +23,11 @@ LL | macro_rules! f { () => (n) } LL | println!("{}", f!()); | ---- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-scope-15167.rs:16:17 + | +LL | if let Some(n) = None { + | ^ = note: this error originates in the macro `f` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `n` in this scope @@ -29,6 +39,11 @@ LL | macro_rules! f { () => (n) } LL | println!("{}", f!()); | ---- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-scope-15167.rs:21:24 + | +LL | } else if let Some(n) = None { + | ^ = note: this error originates in the macro `f` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `n` in this scope @@ -40,6 +55,11 @@ LL | macro_rules! f { () => (n) } LL | println!("{}", f!()); | ---- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/macro-hygiene-scope-15167.rs:25:20 + | +LL | while let Some(n) = None { + | ^ = note: this error originates in the macro `f` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/macros/metavar-expressions/concat-hygiene.stderr b/tests/ui/macros/metavar-expressions/concat-hygiene.stderr index f3150d385ee70..9520f5182f4a7 100644 --- a/tests/ui/macros/metavar-expressions/concat-hygiene.stderr +++ b/tests/ui/macros/metavar-expressions/concat-hygiene.stderr @@ -7,6 +7,11 @@ LL | ${concat($lhs, $rhs)} LL | let _another = join!(abc, def); | --------------- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/concat-hygiene.rs:11:9 + | +LL | let abcdef = 1; + | ^^^^^^ = note: this error originates in the macro `join` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 1 previous error diff --git a/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr b/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr index e904b43aaae06..17171ad5c5cc5 100644 --- a/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr +++ b/tests/ui/proc-macro/gen-macro-rules-hygiene.stderr @@ -18,6 +18,11 @@ LL | gen_macro_rules!(); LL | generated!(); | ------------ in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/gen-macro-rules-hygiene.rs:19:13 + | +LL | let local_use = 1; + | ^^^^^^^^^ = note: this error originates in the macro `generated` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `local_def` in this scope diff --git a/tests/ui/proc-macro/mixed-site-span.stderr b/tests/ui/proc-macro/mixed-site-span.stderr index 2d2d55fe148d5..4bb2508416aaa 100644 --- a/tests/ui/proc-macro/mixed-site-span.stderr +++ b/tests/ui/proc-macro/mixed-site-span.stderr @@ -594,6 +594,11 @@ error[E0425]: cannot find value `local_use` in this scope LL | proc_macro_rules!(); | ^^^^^^^^^^^^^^^^^^^ help: a local variable with a similar name exists: `local_def` | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/mixed-site-span.rs:21:13 + | +LL | let local_use = 1; + | ^^^^^^^^^ = note: this error originates in the macro `proc_macro_rules` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `local_def` in this scope diff --git a/tests/ui/proc-macro/weird-hygiene.stderr b/tests/ui/proc-macro/weird-hygiene.stderr index 0cfac3f89a045..aa3ef9556eb16 100644 --- a/tests/ui/proc-macro/weird-hygiene.stderr +++ b/tests/ui/proc-macro/weird-hygiene.stderr @@ -7,6 +7,11 @@ LL | Value = (stringify!($tokens + hidden_ident), 1).1 LL | other!(50); | ---------- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/weird-hygiene.rs:44:9 + | +LL | let hidden_ident = "Hello1"; + | ^^^^^^^^^^^^ = note: this error originates in the macro `inner` which comes from the expansion of the macro `other` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0425]: cannot find value `hidden_ident` in this scope @@ -18,6 +23,11 @@ LL | hidden_ident LL | invoke_it!(25); | -------------- in this macro invocation | +help: an identifier with the same name exists, but is not accessible due to macro hygiene + --> $DIR/weird-hygiene.rs:44:9 + | +LL | let hidden_ident = "Hello1"; + | ^^^^^^^^^^^^ = note: this error originates in the macro `invoke_it` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors From c92ef479343a92212707a4e85c718d4da4a75328 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 6 Nov 2025 10:07:21 +0800 Subject: [PATCH 09/20] Fix suggestion for returning async closures --- compiler/rustc_hir_analysis/src/collect.rs | 15 ++++ ...async-closure-variance-issue-148488.stderr | 6 ++ .../suggest-impl-async-fn-issue-148493.fixed | 64 ++++++++++++++ .../suggest-impl-async-fn-issue-148493.rs | 64 ++++++++++++++ .../suggest-impl-async-fn-issue-148493.stderr | 87 +++++++++++++++++++ 5 files changed, 236 insertions(+) create mode 100644 tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.fixed create mode 100644 tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.rs create mode 100644 tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.stderr diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 6e62e50d3af82..190b57ecea4d0 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1263,6 +1263,21 @@ pub fn suggest_impl_trait<'tcx>( infcx.tcx.lang_items().future_output(), format_as_assoc, ), + ( + infcx.tcx.lang_items().async_fn_trait(), + infcx.tcx.lang_items().async_fn_once_output(), + format_as_parenthesized, + ), + ( + infcx.tcx.lang_items().async_fn_mut_trait(), + infcx.tcx.lang_items().async_fn_once_output(), + format_as_parenthesized, + ), + ( + infcx.tcx.lang_items().async_fn_once_trait(), + infcx.tcx.lang_items().async_fn_once_output(), + format_as_parenthesized, + ), ( infcx.tcx.lang_items().fn_trait(), infcx.tcx.lang_items().fn_once_output(), diff --git a/tests/ui/async-await/async-closures/ice-async-closure-variance-issue-148488.stderr b/tests/ui/async-await/async-closures/ice-async-closure-variance-issue-148488.stderr index ca98d210579f3..507b1e895898d 100644 --- a/tests/ui/async-await/async-closures/ice-async-closure-variance-issue-148488.stderr +++ b/tests/ui/async-await/async-closures/ice-async-closure-variance-issue-148488.stderr @@ -11,6 +11,12 @@ error[E0121]: the placeholder `_` is not allowed within types on item signatures | LL | fn ord() -> _ { | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn ord() -> _ { +LL + fn ord() -> impl AsyncFn() { + | error[E0392]: lifetime parameter `'g` is never used --> $DIR/ice-async-closure-variance-issue-148488.rs:3:10 diff --git a/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.fixed b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.fixed new file mode 100644 index 0000000000000..db5091f6b7f6c --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.fixed @@ -0,0 +1,64 @@ +#![allow(dead_code)] +//@ run-rustfix +//@ edition: 2021 + +// The suggestion should be `impl AsyncFn()` instead of something like `{async closure@...}` + +fn test1() -> impl AsyncFn() { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() + async || {} +} + +fn test2() -> impl AsyncFn(i32) -> i32 { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn(i32) -> i32 + async |x: i32| x + 1 +} + +fn test3() -> impl AsyncFn(i32, i32) -> i32 { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn(i32, i32) -> i32 + async |x: i32, y: i32| x + y +} + +fn test4() -> impl AsyncFn() { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() + async || -> () { () } +} + +fn test5() -> impl AsyncFn() -> i32 { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() -> i32 + let z = 42; + async move || z +} + +fn test6() -> impl AsyncFnMut() -> i32 { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFnMut() -> i32 + let mut x = 0; + async move || { + x += 1; + x + } +} + +fn test7() -> impl AsyncFnOnce() { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFnOnce() + let s = String::from("hello"); + async move || { + drop(s); + } +} + +fn main() {} diff --git a/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.rs b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.rs new file mode 100644 index 0000000000000..1109904398dab --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.rs @@ -0,0 +1,64 @@ +#![allow(dead_code)] +//@ run-rustfix +//@ edition: 2021 + +// The suggestion should be `impl AsyncFn()` instead of something like `{async closure@...}` + +fn test1() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() + async || {} +} + +fn test2() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn(i32) -> i32 + async |x: i32| x + 1 +} + +fn test3() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn(i32, i32) -> i32 + async |x: i32, y: i32| x + y +} + +fn test4() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() + async || -> () { () } +} + +fn test5() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFn() -> i32 + let z = 42; + async move || z +} + +fn test6() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFnMut() -> i32 + let mut x = 0; + async move || { + x += 1; + x + } +} + +fn test7() -> _ { + //~^ ERROR the placeholder `_` is not allowed within types on item signatures for return types + //~| HELP replace with an appropriate return type + //~| SUGGESTION impl AsyncFnOnce() + let s = String::from("hello"); + async move || { + drop(s); + } +} + +fn main() {} diff --git a/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.stderr b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.stderr new file mode 100644 index 0000000000000..abe775cec082b --- /dev/null +++ b/tests/ui/async-await/async-closures/suggest-impl-async-fn-issue-148493.stderr @@ -0,0 +1,87 @@ +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:7:15 + | +LL | fn test1() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test1() -> _ { +LL + fn test1() -> impl AsyncFn() { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:14:15 + | +LL | fn test2() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test2() -> _ { +LL + fn test2() -> impl AsyncFn(i32) -> i32 { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:21:15 + | +LL | fn test3() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test3() -> _ { +LL + fn test3() -> impl AsyncFn(i32, i32) -> i32 { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:28:15 + | +LL | fn test4() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test4() -> _ { +LL + fn test4() -> impl AsyncFn() { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:35:15 + | +LL | fn test5() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test5() -> _ { +LL + fn test5() -> impl AsyncFn() -> i32 { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:43:15 + | +LL | fn test6() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test6() -> _ { +LL + fn test6() -> impl AsyncFnMut() -> i32 { + | + +error[E0121]: the placeholder `_` is not allowed within types on item signatures for return types + --> $DIR/suggest-impl-async-fn-issue-148493.rs:54:15 + | +LL | fn test7() -> _ { + | ^ not allowed in type signatures + | +help: replace with an appropriate return type + | +LL - fn test7() -> _ { +LL + fn test7() -> impl AsyncFnOnce() { + | + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0121`. From 5ed01e95df9be3accd4d46a9b9c315f602299abd Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 6 Nov 2025 19:40:00 -0600 Subject: [PATCH 10/20] Switch hexagon targets to rust-lld lld is a great choice for a default linker. --- .../src/spec/targets/hexagon_unknown_linux_musl.rs | 1 + .../src/spec/targets/hexagon_unknown_none_elf.rs | 1 + .../src/platform-support/hexagon-unknown-linux-musl.md | 6 ++++++ .../rustc/src/platform-support/hexagon-unknown-none-elf.md | 7 +++++++ 4 files changed, 15 insertions(+) diff --git a/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs index 17b371f05e530..82811cda00ce9 100644 --- a/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs +++ b/compiler/rustc_target/src/spec/targets/hexagon_unknown_linux_musl.rs @@ -8,6 +8,7 @@ pub(crate) fn target() -> Target { base.features = "-small-data,+hvx-length128b".into(); base.has_rpath = true; + base.linker = Some("rust-lld".into()); base.linker_flavor = LinkerFlavor::Unix(Cc::Yes); base.c_enum_min_bits = Some(8); diff --git a/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs b/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs index 6379cd30c3559..55ec3697a15e9 100644 --- a/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs +++ b/compiler/rustc_target/src/spec/targets/hexagon_unknown_none_elf.rs @@ -27,6 +27,7 @@ pub(crate) fn target() -> Target { max_atomic_width: Some(32), emit_debug_gdb_scripts: false, c_enum_min_bits: Some(8), + linker: Some("rust-lld".into()), ..Default::default() }, } diff --git a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md index be6e17883f4eb..d74dd843eb259 100644 --- a/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md +++ b/src/doc/rustc/src/platform-support/hexagon-unknown-linux-musl.md @@ -39,6 +39,12 @@ dynamically linked executables. # /opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/bin/qemu-hexagon -L /opt/clang+llvm-18.1.0-cross-hexagon-unknown-linux-musl/x86_64-linux-gnu/target/hexagon-unknown-linux-musl/usr/ ./hello ``` +## Linking + +This target selects `rust-lld` by default. Another option to use is +[eld](https://github.com/qualcomm/eld), which is also provided with +the opensource hexagon toolchain and the Hexagon SDK. + ## Building the target Because it is Tier 3, rust does not yet ship pre-compiled artifacts for this target. diff --git a/src/doc/rustc/src/platform-support/hexagon-unknown-none-elf.md b/src/doc/rustc/src/platform-support/hexagon-unknown-none-elf.md index b07b0bb08d60a..a906e895b7743 100644 --- a/src/doc/rustc/src/platform-support/hexagon-unknown-none-elf.md +++ b/src/doc/rustc/src/platform-support/hexagon-unknown-none-elf.md @@ -25,6 +25,13 @@ Functions marked `extern "C"` use the [Hexagon architecture calling convention]( This target generates PIC ELF binaries. +## Linking + +This target selects `rust-lld` by default. Another option to use is +[eld](https://github.com/qualcomm/eld), which is also provided with +[the opensource hexagon toolchain](https://github.com/quic/toolchain_for_hexagon) +and the Hexagon SDK. + ## Building the target You can build Rust with support for the target by adding it to the `target` From b827732898bc52912d2428c9d2d004a3a22378f7 Mon Sep 17 00:00:00 2001 From: Amy Kwan Date: Fri, 7 Nov 2025 04:36:12 +0000 Subject: [PATCH 11/20] Enable std locking functions on AIX This patch enables the std locking functions on AIX by including AIX on the list of supported targets for the locking functions. Excluding AIX from the std locking functions results to compilation errors such as: ("try_lock() not supported"). --- library/std/src/sys/fs/unix.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/library/std/src/sys/fs/unix.rs b/library/std/src/sys/fs/unix.rs index 129fccdbf4197..cadcfddb0f7f8 100644 --- a/library/std/src/sys/fs/unix.rs +++ b/library/std/src/sys/fs/unix.rs @@ -1296,6 +1296,7 @@ impl File { target_os = "openbsd", target_os = "cygwin", target_os = "illumos", + target_os = "aix", target_vendor = "apple", ))] pub fn lock(&self) -> io::Result<()> { @@ -1321,6 +1322,7 @@ impl File { target_os = "cygwin", target_os = "solaris", target_os = "illumos", + target_os = "aix", target_vendor = "apple", )))] pub fn lock(&self) -> io::Result<()> { @@ -1335,6 +1337,7 @@ impl File { target_os = "openbsd", target_os = "cygwin", target_os = "illumos", + target_os = "aix", target_vendor = "apple", ))] pub fn lock_shared(&self) -> io::Result<()> { @@ -1360,6 +1363,7 @@ impl File { target_os = "cygwin", target_os = "solaris", target_os = "illumos", + target_os = "aix", target_vendor = "apple", )))] pub fn lock_shared(&self) -> io::Result<()> { @@ -1374,6 +1378,7 @@ impl File { target_os = "openbsd", target_os = "cygwin", target_os = "illumos", + target_os = "aix", target_vendor = "apple", ))] pub fn try_lock(&self) -> Result<(), TryLockError> { @@ -1415,6 +1420,7 @@ impl File { target_os = "cygwin", target_os = "solaris", target_os = "illumos", + target_os = "aix", target_vendor = "apple", )))] pub fn try_lock(&self) -> Result<(), TryLockError> { @@ -1432,6 +1438,7 @@ impl File { target_os = "openbsd", target_os = "cygwin", target_os = "illumos", + target_os = "aix", target_vendor = "apple", ))] pub fn try_lock_shared(&self) -> Result<(), TryLockError> { @@ -1473,6 +1480,7 @@ impl File { target_os = "cygwin", target_os = "solaris", target_os = "illumos", + target_os = "aix", target_vendor = "apple", )))] pub fn try_lock_shared(&self) -> Result<(), TryLockError> { @@ -1490,6 +1498,7 @@ impl File { target_os = "openbsd", target_os = "cygwin", target_os = "illumos", + target_os = "aix", target_vendor = "apple", ))] pub fn unlock(&self) -> io::Result<()> { @@ -1515,6 +1524,7 @@ impl File { target_os = "cygwin", target_os = "solaris", target_os = "illumos", + target_os = "aix", target_vendor = "apple", )))] pub fn unlock(&self) -> io::Result<()> { From fc20a28776ae6cd9583031c970e1ad2b377ddd9e Mon Sep 17 00:00:00 2001 From: Marijn Schouten Date: Tue, 28 Oct 2025 12:10:18 +0100 Subject: [PATCH 12/20] Modify contributor email entries in .mailmap Updated email addresses for several contributors in the mailmap. --- .mailmap | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.mailmap b/.mailmap index fc8e83d6493cd..de7001b774276 100644 --- a/.mailmap +++ b/.mailmap @@ -9,7 +9,7 @@ Aaron Todd Abhishek Chanda Abhishek Chanda Abhijeet Bhagat Abroskin Alexander -Adolfo Ochagavía +Adolfo Ochagavía Adrian Heine né Lang Adrien Tétar Ahmed Charles @@ -36,6 +36,7 @@ Amanda Stjerna Amanda Stjerna Amanieu d'Antras Amos Onn +Amos Wenger Ana-Maria Mihalache Anatoly Ikorsky Andre Bogus @@ -276,7 +277,8 @@ Irina Popa Ivan Ivaschenko ivan tkachenko J. J. Weber -Jack Huey +Jack Huey +Jack Huey <31162821+jackh726@users.noreply.github.com> Jacob Jacob Hoffman-Andrews Jacob Greenfield @@ -292,6 +294,8 @@ Jakub Adam Wieczorek Jakub Adam Wieczorek Jakub Adam Wieczorek Jakub Adam Wieczorek +Jakub Adam Wieczorek +Jakub Adam Wieczorek Jakub Beránek James [Undefined] James Deng @@ -303,6 +307,7 @@ Jamie Hill-Daniel Jana Dönszelmann Jana Dönszelmann Jana Dönszelmann +Jane Lusby Jan-Erik Rediger Jaro Fietz Jason Fager @@ -313,6 +318,7 @@ Jason Toffaletti Jason Toffaletti Jauhien Piatlicki Jauhien Piatlicki Jay True Jeremy Letang +Jeremy Soller Jeremy Sorensen Jeremy Stucki Jeremy Stucki @@ -336,6 +342,7 @@ John Kåre Alsaker John Kåre Alsaker John Talling John Van Enk +Jon Gjengset Jonas Tepe Jonathan Bailey Jonathan Chan Kwan Yin @@ -424,7 +431,7 @@ Malo Jaffré Manish Goregaokar Mara Bos Marcell Pardavi -Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> +Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> Marcus Klaas de Vries Margaret Meyerhofer Marijn Schouten @@ -531,6 +538,7 @@ Oliver Scherer Oliver Scherer Oliver Scherer Oliver Scherer +Onur Özkan Onur Özkan Onur Özkan Ömer Sinan Ağacan @@ -591,6 +599,7 @@ Rusty Blitzerr RustyYato Ruud van Asseldonk Ruud van Asseldonk Ryan Leung +Ryan Levick Ryan Scheel Ryan Sullivant Ryan Wiedemann @@ -685,6 +694,8 @@ Weihang Lo Weihang Lo Wesley Wiser whitequark +Will Crichton +Will Crichton William Ting Wim Looman Wim Looman @@ -694,6 +705,8 @@ Xinye Tao Xuefeng Wu Xuefeng Wu Xuefeng Wu XuefengWu York Xiang +Yoshua Wuyts +Yoshua Wuyts <2467194+yoshuawuyts@users.noreply.github.com> Yotam Ofek Youngsoo Son Youngsuk Kim From a7aedeb5c8895c470cb2f693115f10f0961ab40d Mon Sep 17 00:00:00 2001 From: sayantn Date: Thu, 9 Oct 2025 17:08:17 +0530 Subject: [PATCH 13/20] implement SIMD funnel shifts in const-eval --- .../src/interpret/intrinsics/simd.rs | 54 ++++++++++++++++++- .../intrinsics/simd-funnel_shl-too-far.rs | 12 +++++ .../intrinsics/simd-funnel_shl-too-far.stderr | 13 +++++ .../intrinsics/simd-funnel_shr-too-far.rs | 12 +++++ .../intrinsics/simd-funnel_shr-too-far.stderr | 13 +++++ .../tests/pass/intrinsics/portable-simd.rs | 16 +++++- 6 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs create mode 100644 src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr diff --git a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs index 751674f3f068d..bae423840ee1b 100644 --- a/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs +++ b/compiler/rustc_const_eval/src/interpret/intrinsics/simd.rs @@ -3,7 +3,7 @@ use rustc_abi::{BackendRepr, Endian}; use rustc_apfloat::ieee::{Double, Half, Quad, Single}; use rustc_apfloat::{Float, Round}; use rustc_middle::mir::interpret::{InterpErrorKind, Pointer, UndefinedBehaviorInfo}; -use rustc_middle::ty::{FloatTy, SimdAlign}; +use rustc_middle::ty::{FloatTy, ScalarInt, SimdAlign}; use rustc_middle::{bug, err_ub_format, mir, span_bug, throw_unsup_format, ty}; use rustc_span::{Symbol, sym}; use tracing::trace; @@ -744,6 +744,58 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.write_scalar(val, &dest)?; } } + sym::simd_funnel_shl | sym::simd_funnel_shr => { + let (left, _) = self.project_to_simd(&args[0])?; + let (right, _) = self.project_to_simd(&args[1])?; + let (shift, _) = self.project_to_simd(&args[2])?; + let (dest, _) = self.project_to_simd(&dest)?; + + let (len, elem_ty) = args[0].layout.ty.simd_size_and_type(*self.tcx); + let (elem_size, _signed) = elem_ty.int_size_and_signed(*self.tcx); + let elem_size_bits = u128::from(elem_size.bits()); + + let is_left = intrinsic_name == sym::simd_funnel_shl; + + for i in 0..len { + let left = + self.read_scalar(&self.project_index(&left, i)?)?.to_bits(elem_size)?; + let right = + self.read_scalar(&self.project_index(&right, i)?)?.to_bits(elem_size)?; + let shift_bits = + self.read_scalar(&self.project_index(&shift, i)?)?.to_bits(elem_size)?; + + if shift_bits >= elem_size_bits { + throw_ub_format!( + "overflowing shift by {shift_bits} in `{intrinsic_name}` in lane {i}" + ); + } + let inv_shift_bits = u32::try_from(elem_size_bits - shift_bits).unwrap(); + + // A funnel shift left by S can be implemented as `(x << S) | y.unbounded_shr(SIZE - S)`. + // The `unbounded_shr` is needed because otherwise if `S = 0`, it would be `x | y` + // when it should be `x`. + // + // This selects the least-significant `SIZE - S` bits of `x`, followed by the `S` most + // significant bits of `y`. As `left` and `right` both occupy the lower `SIZE` bits, + // we can treat the lower `SIZE` bits as an integer of the right width and use + // the same implementation, but on a zero-extended `x` and `y`. This works because + // `x << S` just pushes the `SIZE-S` MSBs out, and `y >> (SIZE - S)` shifts in + // zeros, as it is zero-extended. To the lower `SIZE` bits, this looks just like a + // funnel shift left. + // + // Note that the `unbounded_sh{l,r}`s are needed only in case we are using this on + // `u128xN` and `inv_shift_bits == 128`. + let result_bits = if is_left { + (left << shift_bits) | right.unbounded_shr(inv_shift_bits) + } else { + left.unbounded_shl(inv_shift_bits) | (right >> shift_bits) + }; + let (result, _overflow) = ScalarInt::truncate_from_uint(result_bits, elem_size); + + let dest = self.project_index(&dest, i)?; + self.write_scalar(result, &dest)?; + } + } // Unsupported intrinsic: skip the return_to_block below. _ => return interp_ok(false), diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs new file mode 100644 index 0000000000000..54cecc23f9e71 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, portable_simd)] + +use std::intrinsics::simd::simd_funnel_shl; +use std::simd::*; + +fn main() { + unsafe { + let x = i32x2::from_array([1, 1]); + let y = i32x2::from_array([100, 0]); + simd_funnel_shl(x, x, y); //~ERROR: overflowing shift by 100 in `simd_funnel_shl` in lane 0 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr new file mode 100644 index 0000000000000..00c7c39a8856f --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shl-too-far.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: overflowing shift by 100 in `simd_funnel_shl` in lane 0 + --> tests/fail/intrinsics/simd-funnel_shl-too-far.rs:LL:CC + | +LL | simd_funnel_shl(x, x, y); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs new file mode 100644 index 0000000000000..6fb2daa488484 --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.rs @@ -0,0 +1,12 @@ +#![feature(core_intrinsics, portable_simd)] + +use std::intrinsics::simd::simd_funnel_shr; +use std::simd::*; + +fn main() { + unsafe { + let x = i32x2::from_array([1, 1]); + let y = i32x2::from_array([20, 40]); + simd_funnel_shr(x, x, y); //~ERROR: overflowing shift by 40 in `simd_funnel_shr` in lane 1 + } +} diff --git a/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr new file mode 100644 index 0000000000000..2f6c6cd71ea6d --- /dev/null +++ b/src/tools/miri/tests/fail/intrinsics/simd-funnel_shr-too-far.stderr @@ -0,0 +1,13 @@ +error: Undefined Behavior: overflowing shift by 40 in `simd_funnel_shr` in lane 1 + --> tests/fail/intrinsics/simd-funnel_shr-too-far.rs:LL:CC + | +LL | simd_funnel_shr(x, x, y); + | ^^^^^^^^^^^^^^^^^^^^^^^^ Undefined Behavior occurred here + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs index 4ecbe167b5253..961a4b82a7e9b 100644 --- a/src/tools/miri/tests/pass/intrinsics/portable-simd.rs +++ b/src/tools/miri/tests/pass/intrinsics/portable-simd.rs @@ -62,7 +62,7 @@ impl PackedSimd { #[rustc_nounwind] pub unsafe fn simd_shuffle_const_generic(x: T, y: T) -> U; -pub fn simd_ops_f16() { +fn simd_ops_f16() { use intrinsics::*; // small hack to make type inference better @@ -273,7 +273,7 @@ fn simd_ops_f64() { assert_eq!(f64x2::from_array([f64::NAN, 0.0]).reduce_min(), 0.0); } -pub fn simd_ops_f128() { +fn simd_ops_f128() { use intrinsics::*; // small hack to make type inference better @@ -454,6 +454,18 @@ fn simd_ops_i32() { 0x3fffffffu32 as i32 ]) ); + + // these values are taken from the doctests of `u32::funnel_shl` and `u32::funnel_shr` + let c = u32x4::splat(0x010000b3); + let d = u32x4::splat(0x2fe78e45); + + unsafe { + assert_eq!(intrinsics::simd_funnel_shl(c, d, u32x4::splat(0)), c); + assert_eq!(intrinsics::simd_funnel_shl(c, d, u32x4::splat(8)), u32x4::splat(0x0000b32f)); + + assert_eq!(intrinsics::simd_funnel_shr(c, d, u32x4::splat(0)), d); + assert_eq!(intrinsics::simd_funnel_shr(c, d, u32x4::splat(8)), u32x4::splat(0xb32fe78e)); + } } fn simd_mask() { From f2794ce6c0742f28e650840bba90df0a7289838e Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 6 Nov 2025 15:08:13 +0100 Subject: [PATCH 14/20] Switch clean `print` methods into a function --- src/librustdoc/html/format.rs | 760 ++++++++++----------- src/librustdoc/html/render/mod.rs | 46 +- src/librustdoc/html/render/print_item.rs | 49 +- src/librustdoc/html/render/sidebar.rs | 7 +- src/librustdoc/html/render/write_shared.rs | 5 +- 5 files changed, 437 insertions(+), 430 deletions(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 856e637a4587b..c2466135a2cdc 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -47,71 +47,69 @@ pub(crate) fn print_generic_bounds( bounds .iter() .filter(move |b| bounds_dup.insert(*b)) - .map(|bound| bound.print(cx)) + .map(|bound| print_generic_bound(bound, cx)) .joined(" + ", f) }) } -impl clean::GenericParamDef { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match &self.kind { - clean::GenericParamDefKind::Lifetime { outlives } => { - write!(f, "{}", self.name)?; - - if !outlives.is_empty() { - f.write_str(": ")?; - outlives.iter().map(|lt| lt.print()).joined(" + ", f)?; - } +pub(crate) fn print_generic_param_def( + generic_param: &clean::GenericParamDef, + cx: &Context<'_>, +) -> impl Display { + fmt::from_fn(move |f| match &generic_param.kind { + clean::GenericParamDefKind::Lifetime { outlives } => { + write!(f, "{}", generic_param.name)?; - Ok(()) + if !outlives.is_empty() { + f.write_str(": ")?; + outlives.iter().map(|lt| print_lifetime(lt)).joined(" + ", f)?; } - clean::GenericParamDefKind::Type { bounds, default, .. } => { - f.write_str(self.name.as_str())?; - if !bounds.is_empty() { - f.write_str(": ")?; - print_generic_bounds(bounds, cx).fmt(f)?; - } + Ok(()) + } + clean::GenericParamDefKind::Type { bounds, default, .. } => { + f.write_str(generic_param.name.as_str())?; - if let Some(ty) = default { - f.write_str(" = ")?; - ty.print(cx).fmt(f)?; - } + if !bounds.is_empty() { + f.write_str(": ")?; + print_generic_bounds(bounds, cx).fmt(f)?; + } - Ok(()) + if let Some(ty) = default { + f.write_str(" = ")?; + print_type(ty, cx).fmt(f)?; } - clean::GenericParamDefKind::Const { ty, default, .. } => { - write!(f, "const {}: ", self.name)?; - ty.print(cx).fmt(f)?; - - if let Some(default) = default { - f.write_str(" = ")?; - if f.alternate() { - write!(f, "{default}")?; - } else { - write!(f, "{}", Escape(default))?; - } - } - Ok(()) + Ok(()) + } + clean::GenericParamDefKind::Const { ty, default, .. } => { + write!(f, "const {}: ", generic_param.name)?; + print_type(ty, cx).fmt(f)?; + + if let Some(default) = default { + f.write_str(" = ")?; + if f.alternate() { + write!(f, "{default}")?; + } else { + write!(f, "{}", Escape(default))?; + } } - }) - } -} -impl clean::Generics { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - let mut real_params = self.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); - if real_params.peek().is_none() { - None - } else { - Some( - Wrapped::with_angle_brackets() - .wrap_fn(move |f| real_params.clone().map(|g| g.print(cx)).joined(", ", f)), - ) + Ok(()) } - .maybe_display() + }) +} + +pub(crate) fn print_generics(generics: &clean::Generics, cx: &Context<'_>) -> impl Display { + let mut real_params = generics.params.iter().filter(|p| !p.is_synthetic_param()).peekable(); + if real_params.peek().is_none() { + None + } else { + Some(Wrapped::with_angle_brackets().wrap_fn(move |f| { + real_params.clone().map(|g| print_generic_param_def(g, cx)).joined(", ", f) + })) } + .maybe_display() } #[derive(Clone, Copy, PartialEq, Eq)] @@ -125,7 +123,7 @@ fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> match predicate { clean::WherePredicate::BoundPredicate { ty, bounds, bound_params } => { print_higher_ranked_params_with_space(bound_params, cx, "for").fmt(f)?; - ty.print(cx).fmt(f)?; + print_type(ty, cx).fmt(f)?; f.write_str(":")?; if !bounds.is_empty() { f.write_str(" ")?; @@ -136,7 +134,7 @@ fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> clean::WherePredicate::RegionPredicate { lifetime, bounds } => { // We don't need to check `alternate` since we can be certain that neither // the lifetime nor the bounds contain any characters which need escaping. - write!(f, "{}:", lifetime.print())?; + write!(f, "{}:", print_lifetime(lifetime))?; if !bounds.is_empty() { write!(f, " {}", print_generic_bounds(bounds, cx))?; } @@ -144,7 +142,12 @@ fn print_where_predicate(predicate: &clean::WherePredicate, cx: &Context<'_>) -> } clean::WherePredicate::EqPredicate { lhs, rhs } => { let opts = WithOpts::from(f); - write!(f, "{} == {}", opts.display(lhs.print(cx)), opts.display(rhs.print(cx))) + write!( + f, + "{} == {}", + opts.display(print_qpath_data(lhs, cx)), + opts.display(print_term(rhs, cx)), + ) } } }) @@ -229,92 +232,91 @@ pub(crate) fn print_where_clause( })) } -impl clean::Lifetime { - pub(crate) fn print(&self) -> impl Display { - self.0.as_str() - } +#[inline] +pub(crate) fn print_lifetime(lt: &clean::Lifetime) -> &str { + lt.0.as_str() } -impl clean::ConstantKind { - pub(crate) fn print(&self, tcx: TyCtxt<'_>) -> impl Display { - let expr = self.expr(tcx); - fmt::from_fn(move |f| { +pub(crate) fn print_constant_kind( + constant_kind: &clean::ConstantKind, + tcx: TyCtxt<'_>, +) -> impl Display { + let expr = constant_kind.expr(tcx); + fmt::from_fn( + move |f| { if f.alternate() { f.write_str(&expr) } else { write!(f, "{}", Escape(&expr)) } - }) - } + }, + ) } -impl clean::PolyTrait { - fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - print_higher_ranked_params_with_space(&self.generic_params, cx, "for").fmt(f)?; - self.trait_.print(cx).fmt(f) - }) - } +fn print_poly_trait(poly_trait: &clean::PolyTrait, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| { + print_higher_ranked_params_with_space(&poly_trait.generic_params, cx, "for").fmt(f)?; + print_path(&poly_trait.trait_, cx).fmt(f) + }) } -impl clean::GenericBound { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| match self { - clean::GenericBound::Outlives(lt) => write!(f, "{}", lt.print()), - clean::GenericBound::TraitBound(ty, modifiers) => { - // `const` and `[const]` trait bounds are experimental; don't render them. - let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers; - f.write_str(match polarity { - hir::BoundPolarity::Positive => "", - hir::BoundPolarity::Maybe(_) => "?", - hir::BoundPolarity::Negative(_) => "!", - })?; - ty.print(cx).fmt(f) - } - clean::GenericBound::Use(args) => { - f.write_str("use")?; - Wrapped::with_angle_brackets() - .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f)) - .fmt(f) - } - }) - } +pub(crate) fn print_generic_bound( + generic_bound: &clean::GenericBound, + cx: &Context<'_>, +) -> impl Display { + fmt::from_fn(move |f| match generic_bound { + clean::GenericBound::Outlives(lt) => f.write_str(print_lifetime(lt)), + clean::GenericBound::TraitBound(ty, modifiers) => { + // `const` and `[const]` trait bounds are experimental; don't render them. + let hir::TraitBoundModifiers { polarity, constness: _ } = modifiers; + f.write_str(match polarity { + hir::BoundPolarity::Positive => "", + hir::BoundPolarity::Maybe(_) => "?", + hir::BoundPolarity::Negative(_) => "!", + })?; + print_poly_trait(ty, cx).fmt(f) + } + clean::GenericBound::Use(args) => { + f.write_str("use")?; + Wrapped::with_angle_brackets() + .wrap_fn(|f| args.iter().map(|arg| arg.name()).joined(", ", f)) + .fmt(f) + } + }) } -impl clean::GenericArgs { - fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - match self { - clean::GenericArgs::AngleBracketed { args, constraints } => { - if !args.is_empty() || !constraints.is_empty() { - Wrapped::with_angle_brackets() - .wrap_fn(|f| { - [Either::Left(args), Either::Right(constraints)] - .into_iter() - .flat_map(Either::factor_into_iter) - .map(|either| { - either.map_either( - |arg| arg.print(cx), - |constraint| constraint.print(cx), - ) - }) - .joined(", ", f) - }) - .fmt(f)?; - } - } - clean::GenericArgs::Parenthesized { inputs, output } => { - Wrapped::with_parens() - .wrap_fn(|f| inputs.iter().map(|ty| ty.print(cx)).joined(", ", f)) +fn print_generic_args(generic_args: &clean::GenericArgs, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| { + match generic_args { + clean::GenericArgs::AngleBracketed { args, constraints } => { + if !args.is_empty() || !constraints.is_empty() { + Wrapped::with_angle_brackets() + .wrap_fn(|f| { + [Either::Left(args), Either::Right(constraints)] + .into_iter() + .flat_map(Either::factor_into_iter) + .map(|either| { + either.map_either( + |arg| print_generic_arg(arg, cx), + |constraint| print_assoc_item_constraint(constraint, cx), + ) + }) + .joined(", ", f) + }) .fmt(f)?; - if let Some(ref ty) = *output { - f.write_str(if f.alternate() { " -> " } else { " -> " })?; - ty.print(cx).fmt(f)?; - } } - clean::GenericArgs::ReturnTypeNotation => { - f.write_str("(..)")?; + } + clean::GenericArgs::Parenthesized { inputs, output } => { + Wrapped::with_parens() + .wrap_fn(|f| inputs.iter().map(|ty| print_type(ty, cx)).joined(", ", f)) + .fmt(f)?; + if let Some(ref ty) = *output { + f.write_str(if f.alternate() { " -> " } else { " -> " })?; + print_type(ty, cx).fmt(f)?; } } - Ok(()) - }) - } + clean::GenericArgs::ReturnTypeNotation => { + f.write_str("(..)")?; + } + } + Ok(()) + }) } // Possible errors when computing href link source for a `DefId` @@ -684,7 +686,7 @@ fn resolved_path( } } if w.alternate() { - write!(w, "{}{:#}", last.name, last.args.print(cx))?; + write!(w, "{}{:#}", last.name, print_generic_args(&last.args, cx))?; } else { let path = fmt::from_fn(|f| { if use_absolute { @@ -702,7 +704,7 @@ fn resolved_path( write!(f, "{}", print_anchor(did, last.name, cx)) } }); - write!(w, "{path}{args}", args = last.args.print(cx))?; + write!(w, "{path}{args}", args = print_generic_args(&last.args, cx))?; } Ok(()) } @@ -791,11 +793,11 @@ fn print_tybounds( cx: &Context<'_>, ) -> impl Display { fmt::from_fn(move |f| { - bounds.iter().map(|bound| bound.print(cx)).joined(" + ", f)?; + bounds.iter().map(|bound| print_poly_trait(bound, cx)).joined(" + ", f)?; if let Some(lt) = lt { // We don't need to check `alternate` since we can be certain that // the lifetime doesn't contain any characters which need escaping. - write!(f, " + {}", lt.print())?; + write!(f, " + {}", print_lifetime(lt))?; } Ok(()) }) @@ -810,7 +812,9 @@ fn print_higher_ranked_params_with_space( if !params.is_empty() { f.write_str(keyword)?; Wrapped::with_angle_brackets() - .wrap_fn(|f| params.iter().map(|lt| lt.print(cx)).joined(", ", f)) + .wrap_fn(|f| { + params.iter().map(|lt| print_generic_param_def(lt, cx)).joined(", ", f) + }) .fmt(f)?; f.write_char(' ')?; } @@ -868,11 +872,11 @@ fn fmt_type( } else { primitive_link(f, PrimitiveType::Fn, format_args!("fn"), cx)?; } - decl.decl.print(cx).fmt(f) + print_fn_decl(&decl.decl, cx).fmt(f) } clean::UnsafeBinder(binder) => { print_higher_ranked_params_with_space(&binder.generic_params, cx, "unsafe").fmt(f)?; - binder.ty.print(cx).fmt(f) + print_type(&binder.ty, cx).fmt(f) } clean::Tuple(typs) => match &typs[..] { &[] => primitive_link(f, PrimitiveType::Unit, format_args!("()"), cx), @@ -881,7 +885,7 @@ fn fmt_type( primitive_link(f, PrimitiveType::Tuple, format_args!("({name},)"), cx) } else { write!(f, "(")?; - one.print(cx).fmt(f)?; + print_type(one, cx).fmt(f)?; write!(f, ",)") } } @@ -907,7 +911,7 @@ fn fmt_type( ) } else { Wrapped::with_parens() - .wrap_fn(|f| many.iter().map(|item| item.print(cx)).joined(", ", f)) + .wrap_fn(|f| many.iter().map(|item| print_type(item, cx)).joined(", ", f)) .fmt(f) } } @@ -915,9 +919,9 @@ fn fmt_type( clean::Slice(box clean::Generic(name)) => { primitive_link(f, PrimitiveType::Slice, format_args!("[{name}]"), cx) } - clean::Slice(t) => Wrapped::with_square_brackets().wrap(t.print(cx)).fmt(f), + clean::Slice(t) => Wrapped::with_square_brackets().wrap(print_type(t, cx)).fmt(f), clean::Type::Pat(t, pat) => { - fmt::Display::fmt(&t.print(cx), f)?; + fmt::Display::fmt(&print_type(t, cx), f)?; write!(f, " is {pat}") } clean::Array(box clean::Generic(name), n) if !f.alternate() => primitive_link( @@ -928,7 +932,7 @@ fn fmt_type( ), clean::Array(t, n) => Wrapped::with_square_brackets() .wrap(fmt::from_fn(|f| { - t.print(cx).fmt(f)?; + print_type(t, cx).fmt(f)?; f.write_str("; ")?; if f.alternate() { f.write_str(n) @@ -944,17 +948,17 @@ fn fmt_type( primitive_link( f, clean::PrimitiveType::RawPointer, - format_args!("*{m} {ty}", ty = WithOpts::from(f).display(t.print(cx))), + format_args!("*{m} {ty}", ty = WithOpts::from(f).display(print_type(t, cx))), cx, ) } else { primitive_link(f, clean::PrimitiveType::RawPointer, format_args!("*{m} "), cx)?; - t.print(cx).fmt(f) + print_type(t, cx).fmt(f) } } clean::BorrowedRef { lifetime: l, mutability, type_: ty } => { let lt = fmt::from_fn(|f| match l { - Some(l) => write!(f, "{} ", l.print()), + Some(l) => write!(f, "{} ", print_lifetime(l)), _ => Ok(()), }); let m = mutability.print_with_space(); @@ -989,133 +993,133 @@ fn fmt_type( f.write_str("impl ")?; print_generic_bounds(bounds, cx).fmt(f) } - clean::QPath(qpath) => qpath.print(cx).fmt(f), + clean::QPath(qpath) => print_qpath_data(qpath, cx).fmt(f), } } -impl clean::Type { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| fmt_type(self, f, false, cx)) - } +pub(crate) fn print_type(type_: &clean::Type, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| fmt_type(type_, f, false, cx)) } -impl clean::Path { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| resolved_path(f, self.def_id(), self, false, false, cx)) - } +pub(crate) fn print_path(path: &clean::Path, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| resolved_path(f, path.def_id(), path, false, false, cx)) } -impl clean::QPathData { - fn print(&self, cx: &Context<'_>) -> impl Display { - let Self { ref assoc, ref self_type, should_fully_qualify, ref trait_ } = *self; +fn print_qpath_data(qpath_data: &clean::QPathData, cx: &Context<'_>) -> impl Display { + let clean::QPathData { ref assoc, ref self_type, should_fully_qualify, ref trait_ } = + *qpath_data; - fmt::from_fn(move |f| { - // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719), - // we need to surround them with angle brackets in some cases (e.g. `::P`). + fmt::from_fn(move |f| { + // FIXME(inherent_associated_types): Once we support non-ADT self-types (#106719), + // we need to surround them with angle brackets in some cases (e.g. `::P`). - if let Some(trait_) = trait_ - && should_fully_qualify - { - let opts = WithOpts::from(f); - Wrapped::with_angle_brackets() - .wrap(format_args!( - "{} as {}", - opts.display(self_type.print(cx)), - opts.display(trait_.print(cx)) - )) - .fmt(f)? - } else { - self_type.print(cx).fmt(f)?; - } - f.write_str("::")?; - // It's pretty unsightly to look at `::C` in output, and - // we've got hyperlinking on our side, so try to avoid longer - // notation as much as possible by making `C` a hyperlink to trait - // `B` to disambiguate. - // - // FIXME: this is still a lossy conversion and there should probably - // be a better way of representing this in general? Most of - // the ugliness comes from inlining across crates where - // everything comes in as a fully resolved QPath (hard to - // look at). - if !f.alternate() { - // FIXME(inherent_associated_types): We always link to the very first associated - // type (in respect to source order) that bears the given name (`assoc.name`) and that is - // affiliated with the computed `DefId`. This is obviously incorrect when we have - // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself - // through here and map it to the corresponding HTML ID that was generated by - // `render::Context::derive_id` when the impl blocks were rendered. - // There is no such mapping unfortunately. - // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking - // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()` - // considering privacy, `doc(hidden)`, etc. - // I don't feel like that right now :cold_sweat:. - - let parent_href = match trait_ { - Some(trait_) => href(trait_.def_id(), cx).ok(), - None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()), - }; + if let Some(trait_) = trait_ + && should_fully_qualify + { + let opts = WithOpts::from(f); + Wrapped::with_angle_brackets() + .wrap(format_args!( + "{} as {}", + opts.display(print_type(self_type, cx)), + opts.display(print_path(trait_, cx)) + )) + .fmt(f)? + } else { + print_type(self_type, cx).fmt(f)?; + } + f.write_str("::")?; + // It's pretty unsightly to look at `::C` in output, and + // we've got hyperlinking on our side, so try to avoid longer + // notation as much as possible by making `C` a hyperlink to trait + // `B` to disambiguate. + // + // FIXME: this is still a lossy conversion and there should probably + // be a better way of representing this in general? Most of + // the ugliness comes from inlining across crates where + // everything comes in as a fully resolved QPath (hard to + // look at). + if !f.alternate() { + // FIXME(inherent_associated_types): We always link to the very first associated + // type (in respect to source order) that bears the given name (`assoc.name`) and that is + // affiliated with the computed `DefId`. This is obviously incorrect when we have + // multiple impl blocks. Ideally, we would thread the `DefId` of the assoc ty itself + // through here and map it to the corresponding HTML ID that was generated by + // `render::Context::derive_id` when the impl blocks were rendered. + // There is no such mapping unfortunately. + // As a hack, we could badly imitate `derive_id` here by keeping *count* when looking + // for the assoc ty `DefId` in `tcx.associated_items(self_ty_did).in_definition_order()` + // considering privacy, `doc(hidden)`, etc. + // I don't feel like that right now :cold_sweat:. + + let parent_href = match trait_ { + Some(trait_) => href(trait_.def_id(), cx).ok(), + None => self_type.def_id(cx.cache()).and_then(|did| href(did, cx).ok()), + }; - if let Some((url, _, path)) = parent_href { - write!( - f, - "{name}", - shortty = ItemType::AssocType, - name = assoc.name, - path = join_path_syms(path), - ) - } else { - write!(f, "{}", assoc.name) - } + if let Some((url, _, path)) = parent_href { + write!( + f, + "{name}", + shortty = ItemType::AssocType, + name = assoc.name, + path = join_path_syms(path), + ) } else { write!(f, "{}", assoc.name) - }?; + } + } else { + write!(f, "{}", assoc.name) + }?; - assoc.args.print(cx).fmt(f) - }) - } + print_generic_args(&assoc.args, cx).fmt(f) + }) } -impl clean::Impl { - pub(crate) fn print(&self, use_absolute: bool, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - f.write_str("impl")?; - self.generics.print(cx).fmt(f)?; - f.write_str(" ")?; +pub(crate) fn print_impl( + impl_: &clean::Impl, + use_absolute: bool, + cx: &Context<'_>, +) -> impl Display { + fmt::from_fn(move |f| { + f.write_str("impl")?; + print_generics(&impl_.generics, cx).fmt(f)?; + f.write_str(" ")?; - if let Some(ref ty) = self.trait_ { - if self.is_negative_trait_impl() { - f.write_char('!')?; - } - if self.kind.is_fake_variadic() - && let Some(generics) = ty.generics() - && let Ok(inner_type) = generics.exactly_one() - { - let last = ty.last(); - if f.alternate() { - write!(f, "{last}")?; - } else { - write!(f, "{}", print_anchor(ty.def_id(), last, cx))?; - }; - Wrapped::with_angle_brackets() - .wrap_fn(|f| self.print_type(inner_type, f, use_absolute, cx)) - .fmt(f)?; - } else { - ty.print(cx).fmt(f)?; - } - f.write_str(" for ")?; + if let Some(ref ty) = impl_.trait_ { + if impl_.is_negative_trait_impl() { + f.write_char('!')?; } - - if let Some(ty) = self.kind.as_blanket_ty() { - fmt_type(ty, f, use_absolute, cx)?; + if impl_.kind.is_fake_variadic() + && let Some(generics) = ty.generics() + && let Ok(inner_type) = generics.exactly_one() + { + let last = ty.last(); + if f.alternate() { + write!(f, "{last}")?; + } else { + write!(f, "{}", print_anchor(ty.def_id(), last, cx))?; + }; + Wrapped::with_angle_brackets() + .wrap_fn(|f| impl_.print_type(inner_type, f, use_absolute, cx)) + .fmt(f)?; } else { - self.print_type(&self.for_, f, use_absolute, cx)?; + print_path(ty, cx).fmt(f)?; } + f.write_str(" for ")?; + } - print_where_clause(&self.generics, cx, 0, Ending::Newline).maybe_display().fmt(f) - }) - } + if let Some(ty) = impl_.kind.as_blanket_ty() { + fmt_type(ty, f, use_absolute, cx)?; + } else { + impl_.print_type(&impl_.for_, f, use_absolute, cx)?; + } + + print_where_clause(&impl_.generics, cx, 0, Ending::Newline).maybe_display().fmt(f) + }) +} + +impl clean::Impl { fn print_type( &self, type_: &clean::Type, @@ -1191,7 +1195,7 @@ pub(crate) fn print_params(params: &[clean::Parameter], cx: &Context<'_>) -> imp if let Some(name) = param.name { write!(f, "{name}: ")?; } - param.type_.print(cx).fmt(f) + print_type(¶m.type_, cx).fmt(f) }) }) .joined(", ", f) @@ -1221,76 +1225,73 @@ impl Display for Indent { } } -impl clean::Parameter { - fn print(&self, cx: &Context<'_>) -> impl fmt::Display { - fmt::from_fn(move |f| { - if let Some(self_ty) = self.to_receiver() { - match self_ty { - clean::SelfTy => f.write_str("self"), - clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { - f.write_str(if f.alternate() { "&" } else { "&" })?; - if let Some(lt) = lifetime { - write!(f, "{lt} ", lt = lt.print())?; - } - write!(f, "{mutability}self", mutability = mutability.print_with_space()) - } - _ => { - f.write_str("self: ")?; - self_ty.print(cx).fmt(f) +fn print_parameter(parameter: &clean::Parameter, cx: &Context<'_>) -> impl fmt::Display { + fmt::from_fn(move |f| { + if let Some(self_ty) = parameter.to_receiver() { + match self_ty { + clean::SelfTy => f.write_str("self"), + clean::BorrowedRef { lifetime, mutability, type_: box clean::SelfTy } => { + f.write_str(if f.alternate() { "&" } else { "&" })?; + if let Some(lt) = lifetime { + write!(f, "{lt} ", lt = print_lifetime(lt))?; } + write!(f, "{mutability}self", mutability = mutability.print_with_space()) } - } else { - if self.is_const { - write!(f, "const ")?; - } - if let Some(name) = self.name { - write!(f, "{name}: ")?; + _ => { + f.write_str("self: ")?; + print_type(self_ty, cx).fmt(f) } - self.type_.print(cx).fmt(f) } - }) - } + } else { + if parameter.is_const { + write!(f, "const ")?; + } + if let Some(name) = parameter.name { + write!(f, "{name}: ")?; + } + print_type(¶meter.type_, cx).fmt(f) + } + }) } -impl clean::FnDecl { - pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display { - fmt::from_fn(move |f| { - let ellipsis = if self.c_variadic { ", ..." } else { "" }; - Wrapped::with_parens() - .wrap_fn(|f| { - print_params(&self.inputs, cx).fmt(f)?; - f.write_str(ellipsis) - }) - .fmt(f)?; - self.print_output(cx).fmt(f) - }) - } +fn print_fn_decl(fn_decl: &clean::FnDecl, cx: &Context<'_>) -> impl Display { + fmt::from_fn(move |f| { + let ellipsis = if fn_decl.c_variadic { ", ..." } else { "" }; + Wrapped::with_parens() + .wrap_fn(|f| { + print_params(&fn_decl.inputs, cx).fmt(f)?; + f.write_str(ellipsis) + }) + .fmt(f)?; + fn_decl.print_output(cx).fmt(f) + }) +} - /// * `header_len`: The length of the function header and name. In other words, the number of - /// characters in the function declaration up to but not including the parentheses. - /// This is expected to go into a `
`/`code-header` block, so indentation and newlines
-    ///   are preserved.
-    /// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
-    ///   necessary.
-    pub(crate) fn full_print(
-        &self,
-        header_len: usize,
-        indent: usize,
-        cx: &Context<'_>,
-    ) -> impl Display {
-        fmt::from_fn(move |f| {
-            // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
-            let mut counter = WriteCounter(0);
-            write!(&mut counter, "{:#}", fmt::from_fn(|f| { self.inner_full_print(None, f, cx) }))?;
-            // If the text form was over 80 characters wide, we will line-wrap our output.
-            let line_wrapping_indent =
-                if header_len + counter.0 > 80 { Some(indent) } else { None };
-            // Generate the final output. This happens to accept `{:#}` formatting to get textual
-            // output but in practice it is only formatted with `{}` to get HTML output.
-            self.inner_full_print(line_wrapping_indent, f, cx)
-        })
-    }
+/// * `header_len`: The length of the function header and name. In other words, the number of
+///   characters in the function declaration up to but not including the parentheses.
+///   This is expected to go into a `
`/`code-header` block, so indentation and newlines
+///   are preserved.
+/// * `indent`: The number of spaces to indent each successive line with, if line-wrapping is
+///   necessary.
+pub(crate) fn full_print_fn_decl(
+    fn_decl: &clean::FnDecl,
+    header_len: usize,
+    indent: usize,
+    cx: &Context<'_>,
+) -> impl Display {
+    fmt::from_fn(move |f| {
+        // First, generate the text form of the declaration, with no line wrapping, and count the bytes.
+        let mut counter = WriteCounter(0);
+        write!(&mut counter, "{:#}", fmt::from_fn(|f| { fn_decl.inner_full_print(None, f, cx) }))?;
+        // If the text form was over 80 characters wide, we will line-wrap our output.
+        let line_wrapping_indent = if header_len + counter.0 > 80 { Some(indent) } else { None };
+        // Generate the final output. This happens to accept `{:#}` formatting to get textual
+        // output but in practice it is only formatted with `{}` to get HTML output.
+        fn_decl.inner_full_print(line_wrapping_indent, f, cx)
+    })
+}
 
+impl clean::FnDecl {
     fn inner_full_print(
         &self,
         // For None, the declaration will not be line-wrapped. For Some(n),
@@ -1316,7 +1317,7 @@ impl clean::FnDecl {
                         }
                     });
 
-                    self.inputs.iter().map(|param| param.print(cx)).joined(sep, f)?;
+                    self.inputs.iter().map(|param| print_parameter(param, cx)).joined(sep, f)?;
 
                     if line_wrapping_indent.is_some() {
                         writeln!(f, ",")?
@@ -1348,7 +1349,7 @@ impl clean::FnDecl {
             }
 
             f.write_str(if f.alternate() { " -> " } else { " -> " })?;
-            self.output.print(cx).fmt(f)
+            print_type(&self.output, cx).fmt(f)
         })
     }
 }
@@ -1461,67 +1462,68 @@ pub(crate) fn print_constness_with_space(
     }
 }
 
-impl clean::Import {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| match self.kind {
-            clean::ImportKind::Simple(name) => {
-                if name == self.source.path.last() {
-                    write!(f, "use {};", self.source.print(cx))
-                } else {
-                    write!(f, "use {source} as {name};", source = self.source.print(cx))
-                }
+pub(crate) fn print_import(import: &clean::Import, cx: &Context<'_>) -> impl Display {
+    fmt::from_fn(move |f| match import.kind {
+        clean::ImportKind::Simple(name) => {
+            if name == import.source.path.last() {
+                write!(f, "use {};", print_import_source(&import.source, cx))
+            } else {
+                write!(
+                    f,
+                    "use {source} as {name};",
+                    source = print_import_source(&import.source, cx)
+                )
             }
-            clean::ImportKind::Glob => {
-                if self.source.path.segments.is_empty() {
-                    write!(f, "use *;")
-                } else {
-                    write!(f, "use {}::*;", self.source.print(cx))
-                }
+        }
+        clean::ImportKind::Glob => {
+            if import.source.path.segments.is_empty() {
+                write!(f, "use *;")
+            } else {
+                write!(f, "use {}::*;", print_import_source(&import.source, cx))
             }
-        })
-    }
+        }
+    })
 }
 
-impl clean::ImportSource {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| match self.did {
-            Some(did) => resolved_path(f, did, &self.path, true, false, cx),
-            _ => {
-                for seg in &self.path.segments[..self.path.segments.len() - 1] {
-                    write!(f, "{}::", seg.name)?;
-                }
-                let name = self.path.last();
-                if let hir::def::Res::PrimTy(p) = self.path.res {
-                    primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
-                } else {
-                    f.write_str(name.as_str())?;
-                }
-                Ok(())
+fn print_import_source(import_source: &clean::ImportSource, cx: &Context<'_>) -> impl Display {
+    fmt::from_fn(move |f| match import_source.did {
+        Some(did) => resolved_path(f, did, &import_source.path, true, false, cx),
+        _ => {
+            for seg in &import_source.path.segments[..import_source.path.segments.len() - 1] {
+                write!(f, "{}::", seg.name)?;
             }
-        })
-    }
+            let name = import_source.path.last();
+            if let hir::def::Res::PrimTy(p) = import_source.path.res {
+                primitive_link(f, PrimitiveType::from(p), format_args!("{name}"), cx)?;
+            } else {
+                f.write_str(name.as_str())?;
+            }
+            Ok(())
+        }
+    })
 }
 
-impl clean::AssocItemConstraint {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| {
-            f.write_str(self.assoc.name.as_str())?;
-            self.assoc.args.print(cx).fmt(f)?;
-            match self.kind {
-                clean::AssocItemConstraintKind::Equality { ref term } => {
-                    f.write_str(" = ")?;
-                    term.print(cx).fmt(f)?;
-                }
-                clean::AssocItemConstraintKind::Bound { ref bounds } => {
-                    if !bounds.is_empty() {
-                        f.write_str(": ")?;
-                        print_generic_bounds(bounds, cx).fmt(f)?;
-                    }
+fn print_assoc_item_constraint(
+    assoc_item_constraint: &clean::AssocItemConstraint,
+    cx: &Context<'_>,
+) -> impl Display {
+    fmt::from_fn(move |f| {
+        f.write_str(assoc_item_constraint.assoc.name.as_str())?;
+        print_generic_args(&assoc_item_constraint.assoc.args, cx).fmt(f)?;
+        match assoc_item_constraint.kind {
+            clean::AssocItemConstraintKind::Equality { ref term } => {
+                f.write_str(" = ")?;
+                print_term(term, cx).fmt(f)?;
+            }
+            clean::AssocItemConstraintKind::Bound { ref bounds } => {
+                if !bounds.is_empty() {
+                    f.write_str(": ")?;
+                    print_generic_bounds(bounds, cx).fmt(f)?;
                 }
             }
-            Ok(())
-        })
-    }
+        }
+        Ok(())
+    })
 }
 
 pub(crate) fn print_abi_with_space(abi: ExternAbi) -> impl Display {
@@ -1538,22 +1540,18 @@ pub(crate) fn print_default_space(v: bool) -> &'static str {
     if v { "default " } else { "" }
 }
 
-impl clean::GenericArg {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| match self {
-            clean::GenericArg::Lifetime(lt) => lt.print().fmt(f),
-            clean::GenericArg::Type(ty) => ty.print(cx).fmt(f),
-            clean::GenericArg::Const(ct) => ct.print(cx.tcx()).fmt(f),
-            clean::GenericArg::Infer => Display::fmt("_", f),
-        })
-    }
+fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl Display {
+    fmt::from_fn(move |f| match generic_arg {
+        clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)),
+        clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f),
+        clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
+        clean::GenericArg::Infer => Display::fmt("_", f),
+    })
 }
 
-impl clean::Term {
-    pub(crate) fn print(&self, cx: &Context<'_>) -> impl Display {
-        fmt::from_fn(move |f| match self {
-            clean::Term::Type(ty) => ty.print(cx).fmt(f),
-            clean::Term::Constant(ct) => ct.print(cx.tcx()).fmt(f),
-        })
-    }
+fn print_term(term: &clean::Term, cx: &Context<'_>) -> impl Display {
+    fmt::from_fn(move |f| match term {
+        clean::Term::Type(ty) => print_type(ty, cx).fmt(f),
+        clean::Term::Constant(ct) => print_constant_kind(ct, cx.tcx()).fmt(f),
+    })
 }
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 13178ee4e9934..36990332b2fc8 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -74,8 +74,9 @@ use crate::formats::cache::Cache;
 use crate::formats::item_type::ItemType;
 use crate::html::escape::Escape;
 use crate::html::format::{
-    Ending, HrefError, PrintWithSpace, href, print_abi_with_space, print_constness_with_space,
-    print_default_space, print_generic_bounds, print_where_clause, visibility_print_with_space,
+    Ending, HrefError, PrintWithSpace, full_print_fn_decl, href, print_abi_with_space,
+    print_constness_with_space, print_default_space, print_generic_bounds, print_generics,
+    print_impl, print_path, print_type, print_where_clause, visibility_print_with_space,
 };
 use crate::html::markdown::{
     HeadingOffset, IdMap, Markdown, MarkdownItemInfo, MarkdownSummaryLine,
@@ -1044,8 +1045,8 @@ fn assoc_const(
             vis = visibility_print_with_space(it, cx),
             href = assoc_href_attr(it, link, cx).maybe_display(),
             name = it.name.as_ref().unwrap(),
-            generics = generics.print(cx),
-            ty = ty.print(cx),
+            generics = print_generics(generics, cx),
+            ty = print_type(ty, cx),
         )?;
         if let AssocConstValue::TraitDefault(konst) | AssocConstValue::Impl(konst) = value {
             // FIXME: `.value()` uses `clean::utils::format_integer_with_underscore_sep` under the
@@ -1083,14 +1084,14 @@ fn assoc_type(
             vis = visibility_print_with_space(it, cx),
             href = assoc_href_attr(it, link, cx).maybe_display(),
             name = it.name.as_ref().unwrap(),
-            generics = generics.print(cx),
+            generics = print_generics(generics, cx),
         )?;
         if !bounds.is_empty() {
             write!(w, ": {}", print_generic_bounds(bounds, cx))?;
         }
         // Render the default before the where-clause which aligns with the new recommended style. See #89122.
         if let Some(default) = default {
-            write!(w, " = {}", default.print(cx))?;
+            write!(w, " = {}", print_type(default, cx))?;
         }
         write!(w, "{}", print_where_clause(generics, cx, indent, Ending::NoNewline).maybe_display())
     })
@@ -1128,7 +1129,7 @@ fn assoc_method(
         let href = assoc_href_attr(meth, link, cx).maybe_display();
 
         // NOTE: `{:#}` does not print HTML formatting, `{}` does. So `g.print` can't be reused between the length calculation and `write!`.
-        let generics_len = format!("{:#}", g.print(cx)).len();
+        let generics_len = format!("{:#}", print_generics(g, cx)).len();
         let mut header_len = "fn ".len()
             + vis.len()
             + defaultness.len()
@@ -1155,8 +1156,8 @@ fn assoc_method(
             "{indent}{vis}{defaultness}{constness}{asyncness}{safety}{abi}fn \
             {name}{generics}{decl}{notable_traits}{where_clause}",
             indent = indent_str,
-            generics = g.print(cx),
-            decl = d.full_print(header_len, indent, cx),
+            generics = print_generics(g, cx),
+            decl = full_print_fn_decl(d, header_len, indent, cx),
             where_clause = print_where_clause(g, cx, indent, end_newline).maybe_display(),
         )
     })
@@ -1441,8 +1442,10 @@ fn render_assoc_items_inner(
                 Cow::Borrowed("implementations-list"),
             ),
             AssocItemRender::DerefFor { trait_, type_, .. } => {
-                let id =
-                    cx.derive_id(small_url_encode(format!("deref-methods-{:#}", type_.print(cx))));
+                let id = cx.derive_id(small_url_encode(format!(
+                    "deref-methods-{:#}",
+                    print_type(type_, cx)
+                )));
                 // the `impls.get` above only looks at the outermost type,
                 // and the Deref impl may only be implemented for certain
                 // values of generic parameters.
@@ -1466,8 +1469,8 @@ fn render_assoc_items_inner(
                                 fmt::from_fn(|f| write!(
                                     f,
                                     "Methods from {trait_}<Target = {type_}>",
-                                    trait_ = trait_.print(cx),
-                                    type_ = type_.print(cx),
+                                    trait_ = print_path(trait_, cx),
+                                    type_ = print_type(type_, cx),
                                 )),
                                 &id,
                             )
@@ -1645,7 +1648,7 @@ fn notable_traits_button(ty: &clean::Type, cx: &Context<'_>) -> Optionⓘ",
-                ty = Escape(&format!("{:#}", ty.print(cx))),
+                ty = Escape(&format!("{:#}", print_type(ty, cx))),
             )
         })
     })
@@ -1683,7 +1686,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
                 f,
                 "

Notable traits for {}

\
",
-                impl_.for_.print(cx)
+                print_type(&impl_.for_, cx),
             )?;
             true
         } else {
@@ -1691,7 +1694,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) {
         };
 
         for (impl_, trait_did) in notable_impls {
-            write!(f, "
{}
", impl_.print(false, cx))?; + write!(f, "
{}
", print_impl(impl_, false, cx))?; for it in &impl_.items { let clean::AssocTypeItem(tydef, ..) = &it.kind else { continue; @@ -1724,7 +1727,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { }) .to_string(); - (format!("{:#}", ty.print(cx)), out) + (format!("{:#}", print_type(ty, cx)), out) } fn notable_traits_json<'a>(tys: impl Iterator, cx: &Context<'_>) -> String { @@ -2284,7 +2287,7 @@ fn render_impl_summary( )?; if let Some(use_absolute) = use_absolute { - write!(w, "{}", inner_impl.print(use_absolute, cx))?; + write!(w, "{}", print_impl(inner_impl, use_absolute, cx))?; if show_def_docs { for it in &inner_impl.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = it.kind { @@ -2305,7 +2308,7 @@ fn render_impl_summary( } } } else { - write!(w, "{}", inner_impl.print(false, cx))?; + write!(w, "{}", print_impl(inner_impl, false, cx))?; } w.write_str("")?; @@ -2423,7 +2426,10 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String clean::ItemKind::ImplItem(ref i) if i.trait_.is_some() => { // Alternative format produces no URLs, // so this parameter does nothing. - Some((format!("{:#}", i.for_.print(cx)), get_id_for_impl(cx.tcx(), item.item_id))) + Some(( + format!("{:#}", print_type(&i.for_, cx)), + get_id_for_impl(cx.tcx(), item.item_id), + )) } _ => None, } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 6bb8f2043f4bb..fa7c9e75fdac4 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -31,7 +31,8 @@ use crate::formats::Impl; use crate::formats::item_type::ItemType; use crate::html::escape::{Escape, EscapeBodyTextWithWbr}; use crate::html::format::{ - Ending, PrintWithSpace, print_abi_with_space, print_constness_with_space, print_where_clause, + Ending, PrintWithSpace, full_print_fn_decl, print_abi_with_space, print_constness_with_space, + print_generic_bound, print_generics, print_impl, print_import, print_type, print_where_clause, visibility_print_with_space, }; use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine}; @@ -462,7 +463,7 @@ fn item_module(cx: &Context<'_>, item: &clean::Item, items: &[clean::Item]) -> i "{vis}{imp}
{stab_tags}\ ", vis = visibility_print_with_space(myitem, cx), - imp = import.print(cx) + imp = print_import(import, cx), )?; } _ => { @@ -611,7 +612,7 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp let visibility = visibility_print_with_space(it, cx).to_string(); let name = it.name.unwrap(); - let generics_len = format!("{:#}", f.generics.print(cx)).len(); + let generics_len = format!("{:#}", print_generics(&f.generics, cx)).len(); let header_len = "fn ".len() + visibility.len() + constness.len() @@ -635,10 +636,10 @@ fn item_function(cx: &Context<'_>, it: &clean::Item, f: &clean::Function) -> imp safety = safety, abi = abi, name = name, - generics = f.generics.print(cx), + generics = print_generics(&f.generics, cx), where_clause = print_where_clause(&f.generics, cx, 0, Ending::Newline).maybe_display(), - decl = f.decl.full_print(header_len, 0, cx), + decl = full_print_fn_decl(&f.decl, header_len, 0, cx), ) })?; write!(w, "{}", document(cx, it, None, HeadingOffset::H2)) @@ -673,7 +674,7 @@ fn item_trait(cx: &Context<'_>, it: &clean::Item, t: &clean::Trait) -> impl fmt: safety = t.safety(tcx).print_with_space(), is_auto = if t.is_auto(tcx) { "auto " } else { "" }, name = it.name.unwrap(), - generics = t.generics.print(cx), + generics = print_generics(&t.generics, cx), )?; if !t.generics.where_predicates.is_empty() { @@ -1244,7 +1245,7 @@ fn item_trait_alias( w, "trait {name}{generics} = {bounds}{where_clause};", name = it.name.unwrap(), - generics = t.generics.print(cx), + generics = print_generics(&t.generics, cx), bounds = print_bounds(&t.bounds, true, cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::NoNewline).maybe_display(), @@ -1273,10 +1274,10 @@ fn item_type_alias(cx: &Context<'_>, it: &clean::Item, t: &clean::TypeAlias) -> "{vis}type {name}{generics}{where_clause} = {type_};", vis = visibility_print_with_space(it, cx), name = it.name.unwrap(), - generics = t.generics.print(cx), + generics = print_generics(&t.generics, cx), where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline).maybe_display(), - type_ = t.type_.print(cx), + type_ = print_type(&t.type_, cx), ) })?; @@ -1477,7 +1478,7 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> { } fn print_ty(&self, ty: &'a clean::Type) -> impl Display { - ty.print(self.cx) + print_type(ty, self.cx) } // FIXME (GuillaumeGomez): When is implemented, @@ -1523,7 +1524,7 @@ fn print_tuple_struct_fields(cx: &Context<'_>, s: &[clean::Item]) -> impl Displa .map(|ty| { fmt::from_fn(|f| match ty.kind { clean::StrippedItem(box clean::StructFieldItem(_)) => f.write_str("_"), - clean::StructFieldItem(ref ty) => write!(f, "{}", ty.print(cx)), + clean::StructFieldItem(ref ty) => write!(f, "{}", print_type(ty, cx)), _ => unreachable!(), }) }) @@ -1562,7 +1563,7 @@ impl<'clean> DisplayEnum<'clean> { "{}enum {}{}{}", visibility_print_with_space(it, cx), it.name.unwrap(), - self.generics.print(cx), + print_generics(&self.generics, cx), render_enum_fields( cx, Some(self.generics), @@ -1862,7 +1863,7 @@ fn item_variants( {doc}\ ", f = field.name.unwrap(), - t = ty.print(cx), + t = print_type(ty, cx), doc = document(cx, field, Some(variant), HeadingOffset::H5), )?; } @@ -1956,8 +1957,8 @@ fn item_constant( "{vis}const {name}{generics}: {typ}{where_clause}", vis = visibility_print_with_space(it, cx), name = it.name.unwrap(), - generics = generics.print(cx), - typ = ty.print(cx), + generics = print_generics(generics, cx), + typ = print_type(ty, cx), where_clause = print_where_clause(generics, cx, 0, Ending::NoNewline).maybe_display(), )?; @@ -2102,7 +2103,7 @@ fn item_fields( "{field_name}: {ty}\ \ {doc}", - ty = ty.print(cx), + ty = print_type(ty, cx), doc = document(cx, field, Some(it), HeadingOffset::H3), )?; } @@ -2127,7 +2128,7 @@ fn item_static( safe = safety.map(|safe| safe.prefix_str()).unwrap_or(""), mutability = s.mutability.print_with_space(), name = it.name.unwrap(), - typ = s.type_.print(cx) + typ = print_type(&s.type_, cx) ) })?; @@ -2286,7 +2287,7 @@ fn print_bounds( } } - bounds.iter().map(|p| p.print(cx)).joined(inter_str, f) + bounds.iter().map(|p| print_generic_bound(p, cx)).joined(inter_str, f) })) .maybe_display() } @@ -2307,7 +2308,7 @@ struct ImplString(String); impl ImplString { fn new(i: &Impl, cx: &Context<'_>) -> ImplString { - ImplString(format!("{}", i.inner_impl().print(false, cx))) + ImplString(format!("{}", print_impl(i.inner_impl(), false, cx))) } } @@ -2376,7 +2377,7 @@ fn render_union( write!(f, "{}union {}", visibility_print_with_space(it, cx), it.name.unwrap(),)?; let where_displayed = if let Some(generics) = g { - write!(f, "{}", generics.print(cx))?; + write!(f, "{}", print_generics(generics, cx))?; if let Some(where_clause) = print_where_clause(generics, cx, 0, Ending::Newline) { write!(f, "{where_clause}")?; true @@ -2408,7 +2409,7 @@ fn render_union( " {}{}: {},", visibility_print_with_space(field, cx), field.name.unwrap(), - ty.print(cx) + print_type(ty, cx) )?; } } @@ -2442,7 +2443,7 @@ fn render_struct( it.name.unwrap() )?; if let Some(g) = g { - write!(w, "{}", g.print(cx))?; + write!(w, "{}", print_generics(g, cx))?; } write!( w, @@ -2505,7 +2506,7 @@ fn render_struct_fields( "{tab} {vis}{name}: {ty},", vis = visibility_print_with_space(field, cx), name = field.name.unwrap(), - ty = ty.print(cx) + ty = print_type(ty, cx) )?; } } @@ -2548,7 +2549,7 @@ fn render_struct_fields( w, "{}{}", visibility_print_with_space(field, cx), - ty.print(cx) + print_type(ty, cx), )?; } _ => unreachable!(), diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index b9f5ada417c7f..df9e8631bbdd6 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -13,6 +13,7 @@ use super::{Context, ItemSection, item_ty_to_section}; use crate::clean; use crate::formats::Impl; use crate::formats::item_type::ItemType; +use crate::html::format::{print_path, print_type}; use crate::html::markdown::{IdMap, MarkdownWithToc}; use crate::html::render::print_item::compare_names; @@ -558,8 +559,8 @@ fn sidebar_deref_methods<'a>( }; let title = format!( "Methods from {:#}", - impl_.inner_impl().trait_.as_ref().unwrap().print(cx), - real_target.print(cx), + print_path(impl_.inner_impl().trait_.as_ref().unwrap(), cx), + print_type(real_target, cx), ); // We want links' order to be reproducible so we don't use unstable sort. ret.sort(); @@ -690,7 +691,7 @@ fn sidebar_render_assoc_items( ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", ty::ImplPolarity::Negative => "!", }; - let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx))); + let generated = Link::new(encoded, format!("{prefix}{:#}", print_path(trait_, cx))); if links.insert(generated.clone()) { Some(generated) } else { None } }) .collect::>>(); diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index 3a1db805d01cf..6045b9a77ecae 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -44,6 +44,7 @@ use crate::docfs::PathError; use crate::error::Error; use crate::formats::Impl; use crate::formats::item_type::ItemType; +use crate::html::format::{print_impl, print_path}; use crate::html::layout; use crate::html::render::ordered_json::{EscapedJson, OrderedJson}; use crate::html::render::search_index::{SerializedSearchIndex, build_index}; @@ -605,7 +606,7 @@ impl TypeAliasPart { .inner_impl() .trait_ .as_ref() - .map(|trait_| format!("{:#}", trait_.print(cx))); + .map(|trait_| format!("{:#}", print_path(trait_, cx))); ret = Some(AliasSerializableImpl { text, trait_, @@ -704,7 +705,7 @@ impl TraitAliasPart { None } else { Some(Implementor { - text: imp.inner_impl().print(false, cx).to_string(), + text: print_impl(imp.inner_impl(), false, cx).to_string(), synthetic: imp.inner_impl().kind.is_auto(), types: collect_paths_for_type(&imp.inner_impl().for_, cache), }) From 0f0342949d241c15aaf7bf2741cc5f63cd3fb6f9 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 7 Nov 2025 11:45:29 +0100 Subject: [PATCH 15/20] Replace `Display::fmt` with `write_char` --- src/librustdoc/html/format.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index c2466135a2cdc..337429a6248d9 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -1545,7 +1545,7 @@ fn print_generic_arg(generic_arg: &clean::GenericArg, cx: &Context<'_>) -> impl clean::GenericArg::Lifetime(lt) => f.write_str(print_lifetime(lt)), clean::GenericArg::Type(ty) => print_type(ty, cx).fmt(f), clean::GenericArg::Const(ct) => print_constant_kind(ct, cx.tcx()).fmt(f), - clean::GenericArg::Infer => Display::fmt("_", f), + clean::GenericArg::Infer => f.write_char('_'), }) } From fdced17e1fb1f1cf00486383d5a90f45987c66b6 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Fri, 7 Nov 2025 12:35:12 +0100 Subject: [PATCH 16/20] [bootstrap] Make `--open` option work with `doc src/tools/error_index_generator` --- src/bootstrap/src/core/build_steps/doc.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/build_steps/doc.rs b/src/bootstrap/src/core/build_steps/doc.rs index 6622aae069d5c..d6d6fc67ba5ae 100644 --- a/src/bootstrap/src/core/build_steps/doc.rs +++ b/src/bootstrap/src/core/build_steps/doc.rs @@ -1228,9 +1228,12 @@ impl Step for ErrorIndex { t!(fs::create_dir_all(&out)); tool::ErrorIndex::command(builder, self.compilers) .arg("html") - .arg(out) + .arg(&out) .arg(&builder.version) .run(builder); + + let index = out.join("error-index.html"); + builder.maybe_open_in_browser::(index); } fn metadata(&self) -> Option { From c07f11ac0aa0651dde845fe3b72393867e0527aa Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 7 Nov 2025 14:38:16 +0100 Subject: [PATCH 17/20] don't completely reset `HeadUsages` --- compiler/rustc_type_ir/src/search_graph/mod.rs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_type_ir/src/search_graph/mod.rs b/compiler/rustc_type_ir/src/search_graph/mod.rs index 4f3f140af67d1..8e6376b22ce61 100644 --- a/compiler/rustc_type_ir/src/search_graph/mod.rs +++ b/compiler/rustc_type_ir/src/search_graph/mod.rs @@ -23,7 +23,7 @@ use derive_where::derive_where; #[cfg(feature = "nightly")] use rustc_macros::{Decodable_NoContext, Encodable_NoContext, HashStable_NoContext}; use rustc_type_ir::data_structures::HashMap; -use tracing::{debug, instrument}; +use tracing::{debug, instrument, trace}; mod stack; use stack::{Stack, StackDepth, StackEntry}; @@ -916,6 +916,7 @@ impl, X: Cx> SearchGraph { /// heads from the stack. This may not necessarily mean that we've actually /// reached a fixpoint for that cycle head, which impacts the way we rebase /// provisional cache entries. +#[derive_where(Debug; X: Cx)] enum RebaseReason { NoCycleUsages, Ambiguity(X::AmbiguityInfo), @@ -950,6 +951,7 @@ impl, X: Cx> SearchGraph { /// cache entries to also be ambiguous. This causes some undesirable ambiguity for nested /// goals whose result doesn't actually depend on this cycle head, but that's acceptable /// to me. + #[instrument(level = "trace", skip(self, cx))] fn rebase_provisional_cache_entries( &mut self, cx: X, @@ -969,6 +971,7 @@ impl, X: Cx> SearchGraph { let popped_head = if heads.highest_cycle_head_index() == popped_head_index { heads.remove_highest_cycle_head() } else { + debug_assert!(heads.highest_cycle_head_index() < popped_head_index); return true; }; @@ -1057,6 +1060,8 @@ impl, X: Cx> SearchGraph { new_highest_head_index, )); + trace!(?input, ?entry, "rebased provisional cache entry"); + true }); !entries.is_empty() @@ -1379,7 +1384,8 @@ impl, X: Cx> SearchGraph { } // Clear all provisional cache entries which depend on a previous provisional - // result of this goal and rerun. + // result of this goal and rerun. This does not remove goals which accessed this + // goal without depending on its result. self.clear_dependent_provisional_results_for_rerun(); debug!(?result, "fixpoint changed provisional results"); @@ -1399,7 +1405,12 @@ impl, X: Cx> SearchGraph { // similar to the previous iterations when reevaluating, it's better // for caching if the reevaluation also starts out with `false`. encountered_overflow: false, - usages: None, + // We keep provisional cache entries around if they used this goal + // without depending on its result. + // + // We still need to drop or rebase these cache entries once we've + // finished evaluating this goal. + usages: Some(HeadUsages::default()), candidate_usages: None, }); } From 057127cfee067474de01e4854bc50d1d3e386d9f Mon Sep 17 00:00:00 2001 From: Vrtgs Date: Tue, 14 Oct 2025 18:47:50 +0300 Subject: [PATCH 18/20] update isolate_highest_one for NonZero --- library/core/src/num/nonzero.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/core/src/num/nonzero.rs b/library/core/src/num/nonzero.rs index fcdb65bd45c95..cb8838f1eece6 100644 --- a/library/core/src/num/nonzero.rs +++ b/library/core/src/num/nonzero.rs @@ -660,12 +660,15 @@ macro_rules! nonzero_integer { without modifying the original"] #[inline(always)] pub const fn isolate_highest_one(self) -> Self { - let n = self.get() & (((1 as $Int) << (<$Int>::BITS - 1)).wrapping_shr(self.leading_zeros())); - // SAFETY: // `self` is non-zero, so masking to preserve only the most // significant set bit will result in a non-zero `n`. - unsafe { NonZero::new_unchecked(n) } + // and self.leading_zeros() is always < $INT::BITS since + // at least one of the bits in the number is not zero + unsafe { + let bit = (((1 as $Uint) << (<$Uint>::BITS - 1)).unchecked_shr(self.leading_zeros())); + NonZero::new_unchecked(bit as $Int) + } } /// Returns `self` with only the least significant bit set. From eac0c5ac432a50f46a7ae7833a13dde0c962e5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Fri, 7 Nov 2025 23:04:25 +0100 Subject: [PATCH 19/20] Remove eslint-js from npm dependencies --- package-lock.json | 32 ++++++++++++++++++++++---------- package.json | 4 +++- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0297bf70b63f..72129f115845e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "browser-ui-test": "^0.22.2", "es-check": "^6.2.1", "eslint": "^8.57.1", - "eslint-js": "github:eslint/js", "typescript": "^5.8.3" + }, + "devDependencies": { + "@types/node": "^24.10.0" } }, "node_modules/@babel/code-frame": { @@ -57,6 +59,12 @@ "node": ">= 10" } }, + "node_modules/@caporal/core/node_modules/@types/node": { + "version": "13.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.3.tgz", + "integrity": "sha512-01s+ac4qerwd6RHD+mVbOEsraDHSgUaefQlEdBbUolnQFjKwCr7luvAlEwW1RFojh67u0z4OUTjPn9LEl4zIkA==", + "license": "MIT" + }, "node_modules/@colors/colors": { "version": "1.6.0", "license": "MIT", @@ -225,8 +233,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "13.9.3", - "license": "MIT" + "version": "24.10.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", + "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } }, "node_modules/@types/table": { "version": "5.0.0", @@ -944,13 +957,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-js": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/eslint/js.git#9e5b4fabf073b915abc56d6c14cc24177036d43e", - "workspaces": [ - "packages/*" - ] - }, "node_modules/eslint-scope": { "version": "7.2.2", "license": "BSD-2-Clause", @@ -2575,6 +2581,12 @@ "through": "^2.3.8" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/untildify": { "version": "3.0.3", "license": "MIT", diff --git a/package.json b/package.json index 04e0f6af19a0c..7420c53fd8cca 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "browser-ui-test": "^0.22.2", "es-check": "^6.2.1", "eslint": "^8.57.1", - "eslint-js": "github:eslint/js", "typescript": "^5.8.3" + }, + "devDependencies": { + "@types/node": "^24.10.0" } } From 67802e0494034e1c55205a178b2ee1843e6d39da Mon Sep 17 00:00:00 2001 From: MolecularPilot Date: Thu, 30 Oct 2025 17:36:11 +1100 Subject: [PATCH 20/20] rustc_builtin_macros: rename bench parameter to avoid collisions with user-defined function names --- compiler/rustc_builtin_macros/src/test.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_builtin_macros/src/test.rs b/compiler/rustc_builtin_macros/src/test.rs index 7a189ee1f4d05..b0155fad139b8 100644 --- a/compiler/rustc_builtin_macros/src/test.rs +++ b/compiler/rustc_builtin_macros/src/test.rs @@ -207,30 +207,30 @@ pub(crate) fn expand_test_or_bench( }; let test_fn = if is_bench { - // A simple ident for a lambda - let b = Ident::from_str_and_span("b", attr_sp); - + // avoid name collisions by using the function name within the identifier, see bug #148275 + let bencher_param = + Ident::from_str_and_span(&format!("__bench_{}", fn_.ident.name), attr_sp); cx.expr_call( sp, cx.expr_path(test_path("StaticBenchFn")), thin_vec![ // #[coverage(off)] - // |b| self::test::assert_test_result( + // |__bench_fn_name| self::test::assert_test_result( coverage_off(cx.lambda1( sp, cx.expr_call( sp, cx.expr_path(test_path("assert_test_result")), thin_vec![ - // super::$test_fn(b) + // super::$test_fn(__bench_fn_name) cx.expr_call( ret_ty_sp, cx.expr_path(cx.path(sp, vec![fn_.ident])), - thin_vec![cx.expr_ident(sp, b)], + thin_vec![cx.expr_ident(sp, bencher_param)], ), ], ), - b, + bencher_param, )), // ) ], )