From 011d95bca614a160f414a0d85a6cef9a49df503d Mon Sep 17 00:00:00 2001 From: Jamie Cunliffe Date: Tue, 27 Jan 2026 16:18:12 +0000 Subject: [PATCH 01/15] Neon fast path for str::contains --- library/core/src/str/pattern.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/library/core/src/str/pattern.rs b/library/core/src/str/pattern.rs index b54522fcc886f..25202ffd67313 100644 --- a/library/core/src/str/pattern.rs +++ b/library/core/src/str/pattern.rs @@ -997,7 +997,8 @@ impl<'b> Pattern for &'b str { #[cfg(any( all(target_arch = "x86_64", target_feature = "sse2"), - all(target_arch = "loongarch64", target_feature = "lsx") + all(target_arch = "loongarch64", target_feature = "lsx"), + all(target_arch = "aarch64", target_feature = "neon") ))] if self.len() <= 32 { if let Some(result) = simd_contains(self, haystack) { @@ -1782,7 +1783,8 @@ impl TwoWayStrategy for RejectAndMatch { /// [0]: http://0x80.pl/articles/simd-strfind.html#sse-avx2 #[cfg(any( all(target_arch = "x86_64", target_feature = "sse2"), - all(target_arch = "loongarch64", target_feature = "lsx") + all(target_arch = "loongarch64", target_feature = "lsx"), + all(target_arch = "aarch64", target_feature = "neon") ))] #[inline] fn simd_contains(needle: &str, haystack: &str) -> Option { @@ -1917,7 +1919,8 @@ fn simd_contains(needle: &str, haystack: &str) -> Option { /// Both slices must have the same length. #[cfg(any( all(target_arch = "x86_64", target_feature = "sse2"), - all(target_arch = "loongarch64", target_feature = "lsx") + all(target_arch = "loongarch64", target_feature = "lsx"), + all(target_arch = "aarch64", target_feature = "neon") ))] #[inline] unsafe fn small_slice_eq(x: &[u8], y: &[u8]) -> bool { From d05eb780a9051e6effd2e896946adb4e86291f08 Mon Sep 17 00:00:00 2001 From: joboet Date: Sun, 15 Feb 2026 12:55:04 +0100 Subject: [PATCH 02/15] std: move `exit` out of PAL --- library/std/src/process.rs | 2 +- library/std/src/rt.rs | 2 +- .../std/src/sys/{exit_guard.rs => exit.rs} | 81 ++++++++++++++++++- library/std/src/sys/mod.rs | 2 +- library/std/src/sys/pal/hermit/os.rs | 4 - library/std/src/sys/pal/motor/os.rs | 4 - library/std/src/sys/pal/sgx/abi/mod.rs | 2 +- library/std/src/sys/pal/sgx/os.rs | 4 - library/std/src/sys/pal/solid/os.rs | 4 - library/std/src/sys/pal/teeos/os.rs | 4 - library/std/src/sys/pal/uefi/os.rs | 20 ----- library/std/src/sys/pal/unix/os.rs | 5 -- library/std/src/sys/pal/unsupported/os.rs | 4 - library/std/src/sys/pal/vexos/mod.rs | 1 + library/std/src/sys/pal/vexos/os.rs | 19 ----- library/std/src/sys/pal/wasi/os.rs | 4 - library/std/src/sys/pal/windows/os.rs | 4 - library/std/src/sys/pal/xous/os.rs | 4 - library/std/src/sys/pal/zkvm/os.rs | 4 - 19 files changed, 82 insertions(+), 92 deletions(-) rename library/std/src/sys/{exit_guard.rs => exit.rs} (60%) delete mode 100644 library/std/src/sys/pal/vexos/os.rs diff --git a/library/std/src/process.rs b/library/std/src/process.rs index 6838bb422b0e0..d3f47a01c0ff6 100644 --- a/library/std/src/process.rs +++ b/library/std/src/process.rs @@ -2464,7 +2464,7 @@ impl Child { #[cfg_attr(not(test), rustc_diagnostic_item = "process_exit")] pub fn exit(code: i32) -> ! { crate::rt::cleanup(); - crate::sys::os::exit(code) + crate::sys::exit::exit(code) } /// Terminates the process in an abnormal fashion. diff --git a/library/std/src/rt.rs b/library/std/src/rt.rs index 11c0a0b9daf7b..1e7de695ddae7 100644 --- a/library/std/src/rt.rs +++ b/library/std/src/rt.rs @@ -187,7 +187,7 @@ fn lang_start_internal( cleanup(); // Guard against multiple threads calling `libc::exit` concurrently. // See the documentation for `unique_thread_exit` for more information. - crate::sys::exit_guard::unique_thread_exit(); + crate::sys::exit::unique_thread_exit(); ret_code }) diff --git a/library/std/src/sys/exit_guard.rs b/library/std/src/sys/exit.rs similarity index 60% rename from library/std/src/sys/exit_guard.rs rename to library/std/src/sys/exit.rs index e7d7a478a5baa..53fb92ba077e0 100644 --- a/library/std/src/sys/exit_guard.rs +++ b/library/std/src/sys/exit.rs @@ -19,8 +19,7 @@ cfg_select! { /// * If it is called again on the same thread as the first call, it will abort. /// * If it is called again on a different thread, it will wait in a loop /// (waiting for the process to exit). - #[cfg_attr(any(test, doctest), allow(dead_code))] - pub(crate) fn unique_thread_exit() { + pub fn unique_thread_exit() { use crate::ffi::c_int; use crate::ptr; use crate::sync::atomic::AtomicPtr; @@ -62,9 +61,83 @@ cfg_select! { /// /// Mitigation is ***NOT*** implemented on this platform, either because this platform /// is not affected, or because mitigation is not yet implemented for this platform. - #[cfg_attr(any(test, doctest), allow(dead_code))] - pub(crate) fn unique_thread_exit() { + #[cfg_attr(any(test, doctest), expect(dead_code))] + pub fn unique_thread_exit() { // Mitigation not required on platforms where `exit` is thread-safe. } } } + +pub fn exit(code: i32) -> ! { + cfg_select! { + target_os = "hermit" => { + unsafe { hermit_abi::exit(code) } + } + target_os = "linux" => { + unsafe { + unique_thread_exit(); + libc::exit(code) + } + } + target_os = "motor" => { + moto_rt::process::exit(code) + } + all(target_vendor = "fortanix", target_env = "sgx") => { + crate::sys::pal::abi::exit_with_code(code as _) + } + target_os = "solid_asp3" => { + rtabort!("exit({}) called", code) + } + target_os = "teeos" => { + let _ = code; + panic!("TA should not call `exit`") + } + target_os = "uefi" => { + use r_efi::base::Status; + + use crate::os::uefi::env; + + if let (Some(boot_services), Some(handle)) = + (env::boot_services(), env::try_image_handle()) + { + let boot_services = boot_services.cast::(); + let _ = unsafe { + ((*boot_services.as_ptr()).exit)( + handle.as_ptr(), + Status::from_usize(code as usize), + 0, + crate::ptr::null_mut(), + ) + }; + } + crate::intrinsics::abort() + } + any( + target_family = "unix", + target_os = "wasi", + ) => { + unsafe { libc::exit(code as crate::ffi::c_int) } + } + target_os = "vexos" => { + let _ = code; + + unsafe { + vex_sdk::vexSystemExitRequest(); + + loop { + vex_sdk::vexTasksRun(); + } + } + } + target_os = "windows" => { + unsafe { crate::sys::pal::c::ExitProcess(code as u32) } + } + target_os = "xous" => { + crate::os::xous::ffi::exit(code as u32) + } + _ => { + let _ = code; + crate::intrinsics::abort() + } + } +} diff --git a/library/std/src/sys/mod.rs b/library/std/src/sys/mod.rs index 5436c144d3330..5ad23972860bb 100644 --- a/library/std/src/sys/mod.rs +++ b/library/std/src/sys/mod.rs @@ -11,7 +11,7 @@ pub mod backtrace; pub mod cmath; pub mod env; pub mod env_consts; -pub mod exit_guard; +pub mod exit; pub mod fd; pub mod fs; pub mod io; diff --git a/library/std/src/sys/pal/hermit/os.rs b/library/std/src/sys/pal/hermit/os.rs index 48a7cdcd2f763..05afb41647872 100644 --- a/library/std/src/sys/pal/hermit/os.rs +++ b/library/std/src/sys/pal/hermit/os.rs @@ -57,10 +57,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - unsafe { hermit_abi::exit(code) } -} - pub fn getpid() -> u32 { unsafe { hermit_abi::getpid() as u32 } } diff --git a/library/std/src/sys/pal/motor/os.rs b/library/std/src/sys/pal/motor/os.rs index cdf66e3958dbe..202841a0dbfca 100644 --- a/library/std/src/sys/pal/motor/os.rs +++ b/library/std/src/sys/pal/motor/os.rs @@ -63,10 +63,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - moto_rt::process::exit(code) -} - pub fn getpid() -> u32 { panic!("Pids on Motor OS are u64.") } diff --git a/library/std/src/sys/pal/sgx/abi/mod.rs b/library/std/src/sys/pal/sgx/abi/mod.rs index 1c6c681d4c179..3314f4f3b6223 100644 --- a/library/std/src/sys/pal/sgx/abi/mod.rs +++ b/library/std/src/sys/pal/sgx/abi/mod.rs @@ -96,7 +96,7 @@ extern "C" fn entry(p1: u64, p2: u64, p3: u64, secondary: bool, p4: u64, p5: u64 } } -pub(super) fn exit_with_code(code: isize) -> ! { +pub fn exit_with_code(code: isize) -> ! { if code != 0 { if let Some(mut out) = panic::SgxPanicOutput::new() { let _ = write!(out, "Exited with status code {code}"); diff --git a/library/std/src/sys/pal/sgx/os.rs b/library/std/src/sys/pal/sgx/os.rs index ba47af7ff88d7..dc6352da7c2e6 100644 --- a/library/std/src/sys/pal/sgx/os.rs +++ b/library/std/src/sys/pal/sgx/os.rs @@ -56,10 +56,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - super::abi::exit_with_code(code as _) -} - pub fn getpid() -> u32 { panic!("no pids in SGX") } diff --git a/library/std/src/sys/pal/solid/os.rs b/library/std/src/sys/pal/solid/os.rs index c336a1042da40..aeb1c7f46e52a 100644 --- a/library/std/src/sys/pal/solid/os.rs +++ b/library/std/src/sys/pal/solid/os.rs @@ -63,10 +63,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - rtabort!("exit({}) called", code); -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } diff --git a/library/std/src/sys/pal/teeos/os.rs b/library/std/src/sys/pal/teeos/os.rs index a4b1d3c6ae670..72d14ec7fc9df 100644 --- a/library/std/src/sys/pal/teeos/os.rs +++ b/library/std/src/sys/pal/teeos/os.rs @@ -67,10 +67,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(_code: i32) -> ! { - panic!("TA should not call `exit`") -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } diff --git a/library/std/src/sys/pal/uefi/os.rs b/library/std/src/sys/pal/uefi/os.rs index 5b9785c8371e3..7d54bc9aff131 100644 --- a/library/std/src/sys/pal/uefi/os.rs +++ b/library/std/src/sys/pal/uefi/os.rs @@ -1,13 +1,10 @@ -use r_efi::efi::Status; use r_efi::efi::protocols::{device_path, loaded_image_device_path}; use super::{helpers, unsupported_err}; use crate::ffi::{OsStr, OsString}; use crate::marker::PhantomData; -use crate::os::uefi; use crate::os::uefi::ffi::{OsStrExt, OsStringExt}; use crate::path::{self, PathBuf}; -use crate::ptr::NonNull; use crate::{fmt, io}; const PATHS_SEP: u16 = b';' as u16; @@ -105,23 +102,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - if let (Some(boot_services), Some(handle)) = - (uefi::env::boot_services(), uefi::env::try_image_handle()) - { - let boot_services: NonNull = boot_services.cast(); - let _ = unsafe { - ((*boot_services.as_ptr()).exit)( - handle.as_ptr(), - Status::from_usize(code as usize), - 0, - crate::ptr::null_mut(), - ) - }; - } - crate::intrinsics::abort() -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } diff --git a/library/std/src/sys/pal/unix/os.rs b/library/std/src/sys/pal/unix/os.rs index b8280a8f29a02..494d94433db34 100644 --- a/library/std/src/sys/pal/unix/os.rs +++ b/library/std/src/sys/pal/unix/os.rs @@ -533,11 +533,6 @@ pub fn home_dir() -> Option { } } -pub fn exit(code: i32) -> ! { - crate::sys::exit_guard::unique_thread_exit(); - unsafe { libc::exit(code as c_int) } -} - pub fn getpid() -> u32 { unsafe { libc::getpid() as u32 } } diff --git a/library/std/src/sys/pal/unsupported/os.rs b/library/std/src/sys/pal/unsupported/os.rs index cb925ef4348db..99568458184b6 100644 --- a/library/std/src/sys/pal/unsupported/os.rs +++ b/library/std/src/sys/pal/unsupported/os.rs @@ -56,10 +56,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(_code: i32) -> ! { - crate::intrinsics::abort() -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } diff --git a/library/std/src/sys/pal/vexos/mod.rs b/library/std/src/sys/pal/vexos/mod.rs index 16aa3f088f04b..d1380ab8dff14 100644 --- a/library/std/src/sys/pal/vexos/mod.rs +++ b/library/std/src/sys/pal/vexos/mod.rs @@ -1,3 +1,4 @@ +#[path = "../unsupported/os.rs"] pub mod os; #[expect(dead_code)] diff --git a/library/std/src/sys/pal/vexos/os.rs b/library/std/src/sys/pal/vexos/os.rs deleted file mode 100644 index 303b452a078ff..0000000000000 --- a/library/std/src/sys/pal/vexos/os.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[expect(dead_code)] -#[path = "../unsupported/os.rs"] -mod unsupported_os; -pub use unsupported_os::{ - JoinPathsError, SplitPaths, chdir, current_exe, getcwd, getpid, home_dir, join_paths, - split_paths, temp_dir, -}; - -pub use super::unsupported; - -pub fn exit(_code: i32) -> ! { - unsafe { - vex_sdk::vexSystemExitRequest(); - - loop { - vex_sdk::vexTasksRun(); - } - } -} diff --git a/library/std/src/sys/pal/wasi/os.rs b/library/std/src/sys/pal/wasi/os.rs index 285be3ca9fda4..4a92e577c6503 100644 --- a/library/std/src/sys/pal/wasi/os.rs +++ b/library/std/src/sys/pal/wasi/os.rs @@ -102,10 +102,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - unsafe { libc::exit(code) } -} - pub fn getpid() -> u32 { panic!("unsupported"); } diff --git a/library/std/src/sys/pal/windows/os.rs b/library/std/src/sys/pal/windows/os.rs index 3eb6ec8278401..ebbbb128a7c9b 100644 --- a/library/std/src/sys/pal/windows/os.rs +++ b/library/std/src/sys/pal/windows/os.rs @@ -189,10 +189,6 @@ pub fn home_dir() -> Option { .or_else(home_dir_crt) } -pub fn exit(code: i32) -> ! { - unsafe { c::ExitProcess(code as u32) } -} - pub fn getpid() -> u32 { unsafe { c::GetCurrentProcessId() } } diff --git a/library/std/src/sys/pal/xous/os.rs b/library/std/src/sys/pal/xous/os.rs index cd7b7b59d1127..b915bccc7f7d0 100644 --- a/library/std/src/sys/pal/xous/os.rs +++ b/library/std/src/sys/pal/xous/os.rs @@ -119,10 +119,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(code: i32) -> ! { - crate::os::xous::ffi::exit(code as u32); -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } diff --git a/library/std/src/sys/pal/zkvm/os.rs b/library/std/src/sys/pal/zkvm/os.rs index cb925ef4348db..99568458184b6 100644 --- a/library/std/src/sys/pal/zkvm/os.rs +++ b/library/std/src/sys/pal/zkvm/os.rs @@ -56,10 +56,6 @@ pub fn home_dir() -> Option { None } -pub fn exit(_code: i32) -> ! { - crate::intrinsics::abort() -} - pub fn getpid() -> u32 { panic!("no pids on this platform") } From b0050c24d463e3eaee6ec660a2acf51975ad7804 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 14:36:32 +0100 Subject: [PATCH 03/15] move `must_use` lint to a separate file --- compiler/rustc_lint/src/lib.rs | 3 +- compiler/rustc_lint/src/unused.rs | 571 +-------------------- compiler/rustc_lint/src/unused/must_use.rs | 570 ++++++++++++++++++++ 3 files changed, 578 insertions(+), 566 deletions(-) create mode 100644 compiler/rustc_lint/src/unused/must_use.rs diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index a5c3a889826c7..cd0d8765dd933 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -78,7 +78,7 @@ mod transmute; mod types; mod unit_bindings; mod unqualified_local_imports; -mod unused; +pub mod unused; mod utils; use async_closures::AsyncClosureUsage; @@ -125,6 +125,7 @@ use transmute::CheckTransmutes; use types::*; use unit_bindings::*; use unqualified_local_imports::*; +use unused::must_use::*; use unused::*; #[rustfmt::skip] diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 4b3a49af08369..1fd0ee754eb46 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -1,579 +1,20 @@ -use std::iter; - use rustc_ast::util::{classify, parser}; use rustc_ast::{self as ast, ExprKind, FnRetTy, HasAttrs as _, StmtKind}; use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{MultiSpan, pluralize}; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, LangItem, find_attr}; -use rustc_infer::traits::util::elaborate; -use rustc_middle::ty::{self, Ty, adjustment}; +use rustc_errors::MultiSpan; +use rustc_hir::{self as hir}; +use rustc_middle::ty::{self, adjustment}; use rustc_session::{declare_lint, declare_lint_pass, impl_lint_pass}; use rustc_span::edition::Edition::Edition2015; -use rustc_span::{BytePos, Span, Symbol, kw, sym}; -use tracing::instrument; +use rustc_span::{BytePos, Span, kw, sym}; use crate::lints::{ PathStatementDrop, PathStatementDropSub, PathStatementNoEffect, UnusedAllocationDiag, - UnusedAllocationMutDiag, UnusedClosure, UnusedCoroutine, UnusedDef, UnusedDefSuggestion, - UnusedDelim, UnusedDelimSuggestion, UnusedImportBracesDiag, UnusedOp, UnusedOpSuggestion, - UnusedResult, + UnusedAllocationMutDiag, UnusedDelim, UnusedDelimSuggestion, UnusedImportBracesDiag, }; use crate::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, Lint, LintContext}; -declare_lint! { - /// The `unused_must_use` lint detects unused result of a type flagged as - /// `#[must_use]`. - /// - /// ### Example - /// - /// ```rust - /// fn returns_result() -> Result<(), ()> { - /// Ok(()) - /// } - /// - /// fn main() { - /// returns_result(); - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// The `#[must_use]` attribute is an indicator that it is a mistake to - /// ignore the value. See [the reference] for more details. - /// - /// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute - pub UNUSED_MUST_USE, - Warn, - "unused result of a type flagged as `#[must_use]`", - report_in_external_macro -} - -declare_lint! { - /// The `unused_results` lint checks for the unused result of an - /// expression in a statement. - /// - /// ### Example - /// - /// ```rust,compile_fail - /// #![deny(unused_results)] - /// fn foo() -> T { panic!() } - /// - /// fn main() { - /// foo::(); - /// } - /// ``` - /// - /// {{produces}} - /// - /// ### Explanation - /// - /// Ignoring the return value of a function may indicate a mistake. In - /// cases were it is almost certain that the result should be used, it is - /// recommended to annotate the function with the [`must_use` attribute]. - /// Failure to use such a return value will trigger the [`unused_must_use` - /// lint] which is warn-by-default. The `unused_results` lint is - /// essentially the same, but triggers for *all* return values. - /// - /// This lint is "allow" by default because it can be noisy, and may not be - /// an actual problem. For example, calling the `remove` method of a `Vec` - /// or `HashMap` returns the previous value, which you may not care about. - /// Using this lint would require explicitly ignoring or discarding such - /// values. - /// - /// [`must_use` attribute]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute - /// [`unused_must_use` lint]: warn-by-default.html#unused-must-use - pub UNUSED_RESULTS, - Allow, - "unused result of an expression in a statement" -} - -declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); - -impl<'tcx> LateLintPass<'tcx> for UnusedResults { - fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { - let hir::StmtKind::Semi(mut expr) = s.kind else { - return; - }; - - let mut expr_is_from_block = false; - while let hir::ExprKind::Block(blk, ..) = expr.kind - && let hir::Block { expr: Some(e), .. } = blk - { - expr = e; - expr_is_from_block = true; - } - - if let hir::ExprKind::Ret(..) = expr.kind { - return; - } - - if let hir::ExprKind::Match(await_expr, _arms, hir::MatchSource::AwaitDesugar) = expr.kind - && let ty = cx.typeck_results().expr_ty(await_expr) - && let ty::Alias(ty::Opaque, ty::AliasTy { def_id: future_def_id, .. }) = ty.kind() - && cx.tcx.ty_is_opaque_future(ty) - && let async_fn_def_id = cx.tcx.parent(*future_def_id) - && matches!(cx.tcx.def_kind(async_fn_def_id), DefKind::Fn | DefKind::AssocFn) - // Check that this `impl Future` actually comes from an `async fn` - && cx.tcx.asyncness(async_fn_def_id).is_async() - && check_must_use_def( - cx, - async_fn_def_id, - expr.span, - "output of future returned by ", - "", - expr_is_from_block, - ) - { - // We have a bare `foo().await;` on an opaque type from an async function that was - // annotated with `#[must_use]`. - return; - } - - let ty = cx.typeck_results().expr_ty(expr); - - let must_use_result = is_ty_must_use(cx, ty, expr, expr.span); - let type_lint_emitted_or_suppressed = match must_use_result { - Some(path) => { - emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); - true - } - None => false, - }; - - let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block); - - if !fn_warned && type_lint_emitted_or_suppressed { - // We don't warn about unused unit or uninhabited types. - // (See https://github.com/rust-lang/rust/issues/43806 for details.) - return; - } - - let must_use_op = match expr.kind { - // Hardcoding operators here seemed more expedient than the - // refactoring that would be needed to look up the `#[must_use]` - // attribute which does exist on the comparison trait methods - hir::ExprKind::Binary(bin_op, ..) => match bin_op.node { - hir::BinOpKind::Eq - | hir::BinOpKind::Lt - | hir::BinOpKind::Le - | hir::BinOpKind::Ne - | hir::BinOpKind::Ge - | hir::BinOpKind::Gt => Some("comparison"), - hir::BinOpKind::Add - | hir::BinOpKind::Sub - | hir::BinOpKind::Div - | hir::BinOpKind::Mul - | hir::BinOpKind::Rem => Some("arithmetic operation"), - hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"), - hir::BinOpKind::BitXor - | hir::BinOpKind::BitAnd - | hir::BinOpKind::BitOr - | hir::BinOpKind::Shl - | hir::BinOpKind::Shr => Some("bitwise operation"), - }, - hir::ExprKind::AddrOf(..) => Some("borrow"), - hir::ExprKind::OffsetOf(..) => Some("`offset_of` call"), - hir::ExprKind::Unary(..) => Some("unary operation"), - // The `offset_of` macro wraps its contents inside a `const` block. - hir::ExprKind::ConstBlock(block) => { - let body = cx.tcx.hir_body(block.body); - if let hir::ExprKind::Block(block, _) = body.value.kind - && let Some(expr) = block.expr - && let hir::ExprKind::OffsetOf(..) = expr.kind - { - Some("`offset_of` call") - } else { - None - } - } - _ => None, - }; - - let mut op_warned = false; - - if let Some(must_use_op) = must_use_op { - let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span); - cx.emit_span_lint( - UNUSED_MUST_USE, - expr.span, - UnusedOp { - op: must_use_op, - label: expr.span, - suggestion: if expr_is_from_block { - UnusedOpSuggestion::BlockTailExpr { - before_span: span.shrink_to_lo(), - after_span: span.shrink_to_hi(), - } - } else { - UnusedOpSuggestion::NormalExpr { span: span.shrink_to_lo() } - }, - }, - ); - op_warned = true; - } - - if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { - cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); - } - - fn check_fn_must_use( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - expr_is_from_block: bool, - ) -> bool { - let maybe_def_id = match expr.kind { - hir::ExprKind::Call(callee, _) => { - match callee.kind { - hir::ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, callee.hir_id) { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), - // `Res::Local` if it was a closure, for which we - // do not currently support must-use linting - _ => None, - } - } - _ => None, - } - } - hir::ExprKind::MethodCall(..) => { - cx.typeck_results().type_dependent_def_id(expr.hir_id) - } - _ => None, - }; - if let Some(def_id) = maybe_def_id { - check_must_use_def( - cx, - def_id, - expr.span, - "return value of ", - "", - expr_is_from_block, - ) - } else { - false - } - } - - /// A path through a type to a must_use source. Contains useful info for the lint. - #[derive(Debug)] - enum MustUsePath { - /// Suppress must_use checking. - Suppressed, - /// The root of the normal must_use lint with an optional message. - Def(Span, DefId, Option), - Boxed(Box), - Pinned(Box), - Opaque(Box), - TraitObject(Box), - TupleElement(Vec<(usize, Self)>), - Array(Box, u64), - /// The root of the unused_closures lint. - Closure(Span), - /// The root of the unused_coroutines lint. - Coroutine(Span), - } - - #[instrument(skip(cx, expr), level = "debug", ret)] - fn is_ty_must_use<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - expr: &hir::Expr<'_>, - span: Span, - ) -> Option { - if ty.is_unit() { - return Some(MustUsePath::Suppressed); - } - let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); - let is_uninhabited = - |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); - if is_uninhabited(ty) { - return Some(MustUsePath::Suppressed); - } - - match *ty.kind() { - ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, span) - .map(|inner| MustUsePath::Boxed(Box::new(inner))) - } - ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { - let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, span) - .map(|inner| MustUsePath::Pinned(Box::new(inner))) - } - // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::Result, def.did()) - && args.type_at(0).is_unit() - && is_uninhabited(args.type_at(1)) => - { - Some(MustUsePath::Suppressed) - } - // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) - && args.type_at(1).is_unit() - && is_uninhabited(args.type_at(0)) => - { - Some(MustUsePath::Suppressed) - } - ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), - ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) - // We only care about self bounds for the impl-trait - .filter_only_self() - .find_map(|(pred, _span)| { - // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::ClauseKind::Trait(ref poly_trait_predicate) = - pred.kind().skip_binder() - { - let def_id = poly_trait_predicate.trait_ref.def_id; - - is_def_must_use(cx, def_id, span) - } else { - None - } - }) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } - ty::Dynamic(binders, _) => binders.iter().find_map(|predicate| { - if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() - { - let def_id = trait_ref.def_id; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::TraitObject(Box::new(inner))) - } else { - None - } - }), - ty::Tuple(tys) => { - let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { - debug_assert_eq!(elem_exprs.len(), tys.len()); - elem_exprs - } else { - &[] - }; - - // Default to `expr`. - let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); - - let nested_must_use = tys - .iter() - .zip(elem_exprs) - .enumerate() - .filter_map(|(i, (ty, expr))| { - is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) - }) - .collect::>(); - - if !nested_must_use.is_empty() { - Some(MustUsePath::TupleElement(nested_must_use)) - } else { - None - } - } - ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { - // If the array is empty we don't lint, to avoid false positives - Some(0) | None => None, - // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, span) - .map(|inner| MustUsePath::Array(Box::new(inner), len)), - }, - ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), - ty::Coroutine(def_id, ..) => { - // async fn should be treated as "implementor of `Future`" - let must_use = if cx.tcx.coroutine_is_async(def_id) { - let def_id = cx.tcx.lang_items().future_trait()?; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } else { - None - }; - must_use.or(Some(MustUsePath::Coroutine(span))) - } - _ => None, - } - } - - fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { - if let Some(reason) = find_attr!( - cx.tcx, def_id, - MustUse { reason, .. } => reason - ) { - // check for #[must_use = "..."] - Some(MustUsePath::Def(span, def_id, *reason)) - } else { - None - } - } - - // Returns whether further errors should be suppressed because either a lint has been - // emitted or the type should be ignored. - fn check_must_use_def( - cx: &LateContext<'_>, - def_id: DefId, - span: Span, - descr_pre_path: &str, - descr_post_path: &str, - expr_is_from_block: bool, - ) -> bool { - is_def_must_use(cx, def_id, span) - .map(|must_use_path| { - emit_must_use_untranslated( - cx, - &must_use_path, - descr_pre_path, - descr_post_path, - 1, - false, - expr_is_from_block, - ) - }) - .is_some() - } - - #[instrument(skip(cx), level = "debug")] - fn emit_must_use_untranslated( - cx: &LateContext<'_>, - path: &MustUsePath, - descr_pre: &str, - descr_post: &str, - plural_len: usize, - is_inner: bool, - expr_is_from_block: bool, - ) { - let plural_suffix = pluralize!(plural_len); - - match path { - MustUsePath::Suppressed => {} - MustUsePath::Boxed(path) => { - let descr_pre = &format!("{descr_pre}boxed "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Pinned(path) => { - let descr_pre = &format!("{descr_pre}pinned "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Opaque(path) => { - let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TraitObject(path) => { - let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TupleElement(elems) => { - for (index, path) in elems { - let descr_post = &format!(" in tuple element {index}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - } - MustUsePath::Array(path, len) => { - let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), - true, - expr_is_from_block, - ); - } - MustUsePath::Closure(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Coroutine(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Def(span, def_id, reason) => { - let ancenstor_span = span.find_ancestor_not_from_macro().unwrap_or(*span); - let is_redundant_let_ignore = cx - .sess() - .source_map() - .span_to_prev_source(ancenstor_span) - .ok() - .map(|prev| prev.trim_end().ends_with("let _ =")) - .unwrap_or(false); - let suggestion_span = - if is_redundant_let_ignore { *span } else { ancenstor_span }; - cx.emit_span_lint( - UNUSED_MUST_USE, - ancenstor_span, - UnusedDef { - pre: descr_pre, - post: descr_post, - cx, - def_id: *def_id, - note: *reason, - suggestion: (!is_inner).then_some(if expr_is_from_block { - UnusedDefSuggestion::BlockTailExpr { - before_span: suggestion_span.shrink_to_lo(), - after_span: suggestion_span.shrink_to_hi(), - } - } else { - UnusedDefSuggestion::NormalExpr { - span: suggestion_span.shrink_to_lo(), - } - }), - }, - ); - } - } - } - } -} +pub mod must_use; declare_lint! { /// The `path_statements` lint detects path statements with no effect. diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs new file mode 100644 index 0000000000000..e80e6412dae86 --- /dev/null +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -0,0 +1,570 @@ +use std::iter; + +use rustc_errors::pluralize; +use rustc_hir::def::{DefKind, Res}; +use rustc_hir::def_id::DefId; +use rustc_hir::{self as hir, LangItem, find_attr}; +use rustc_infer::traits::util::elaborate; +use rustc_middle::ty::{self, Ty}; +use rustc_session::{declare_lint, declare_lint_pass}; +use rustc_span::{Span, Symbol, sym}; +use tracing::instrument; + +use crate::lints::{ + UnusedClosure, UnusedCoroutine, UnusedDef, UnusedDefSuggestion, UnusedOp, UnusedOpSuggestion, + UnusedResult, +}; +use crate::{LateContext, LateLintPass, LintContext}; + +declare_lint! { + /// The `unused_must_use` lint detects unused result of a type flagged as + /// `#[must_use]`. + /// + /// ### Example + /// + /// ```rust + /// fn returns_result() -> Result<(), ()> { + /// Ok(()) + /// } + /// + /// fn main() { + /// returns_result(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// The `#[must_use]` attribute is an indicator that it is a mistake to + /// ignore the value. See [the reference] for more details. + /// + /// [the reference]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + pub UNUSED_MUST_USE, + Warn, + "unused result of a type flagged as `#[must_use]`", + report_in_external_macro +} + +declare_lint! { + /// The `unused_results` lint checks for the unused result of an + /// expression in a statement. + /// + /// ### Example + /// + /// ```rust,compile_fail + /// #![deny(unused_results)] + /// fn foo() -> T { panic!() } + /// + /// fn main() { + /// foo::(); + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Ignoring the return value of a function may indicate a mistake. In + /// cases were it is almost certain that the result should be used, it is + /// recommended to annotate the function with the [`must_use` attribute]. + /// Failure to use such a return value will trigger the [`unused_must_use` + /// lint] which is warn-by-default. The `unused_results` lint is + /// essentially the same, but triggers for *all* return values. + /// + /// This lint is "allow" by default because it can be noisy, and may not be + /// an actual problem. For example, calling the `remove` method of a `Vec` + /// or `HashMap` returns the previous value, which you may not care about. + /// Using this lint would require explicitly ignoring or discarding such + /// values. + /// + /// [`must_use` attribute]: https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute + /// [`unused_must_use` lint]: warn-by-default.html#unused-must-use + pub UNUSED_RESULTS, + Allow, + "unused result of an expression in a statement" +} + +declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); + +impl<'tcx> LateLintPass<'tcx> for UnusedResults { + fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { + let hir::StmtKind::Semi(mut expr) = s.kind else { + return; + }; + + let mut expr_is_from_block = false; + while let hir::ExprKind::Block(blk, ..) = expr.kind + && let hir::Block { expr: Some(e), .. } = blk + { + expr = e; + expr_is_from_block = true; + } + + if let hir::ExprKind::Ret(..) = expr.kind { + return; + } + + if let hir::ExprKind::Match(await_expr, _arms, hir::MatchSource::AwaitDesugar) = expr.kind + && let ty = cx.typeck_results().expr_ty(await_expr) + && let ty::Alias(ty::Opaque, ty::AliasTy { def_id: future_def_id, .. }) = ty.kind() + && cx.tcx.ty_is_opaque_future(ty) + && let async_fn_def_id = cx.tcx.parent(*future_def_id) + && matches!(cx.tcx.def_kind(async_fn_def_id), DefKind::Fn | DefKind::AssocFn) + // Check that this `impl Future` actually comes from an `async fn` + && cx.tcx.asyncness(async_fn_def_id).is_async() + && check_must_use_def( + cx, + async_fn_def_id, + expr.span, + "output of future returned by ", + "", + expr_is_from_block, + ) + { + // We have a bare `foo().await;` on an opaque type from an async function that was + // annotated with `#[must_use]`. + return; + } + + let ty = cx.typeck_results().expr_ty(expr); + + let must_use_result = is_ty_must_use(cx, ty, expr, expr.span); + let type_lint_emitted_or_suppressed = match must_use_result { + Some(path) => { + emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); + true + } + None => false, + }; + + let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block); + + if !fn_warned && type_lint_emitted_or_suppressed { + // We don't warn about unused unit or uninhabited types. + // (See https://github.com/rust-lang/rust/issues/43806 for details.) + return; + } + + let must_use_op = match expr.kind { + // Hardcoding operators here seemed more expedient than the + // refactoring that would be needed to look up the `#[must_use]` + // attribute which does exist on the comparison trait methods + hir::ExprKind::Binary(bin_op, ..) => match bin_op.node { + hir::BinOpKind::Eq + | hir::BinOpKind::Lt + | hir::BinOpKind::Le + | hir::BinOpKind::Ne + | hir::BinOpKind::Ge + | hir::BinOpKind::Gt => Some("comparison"), + hir::BinOpKind::Add + | hir::BinOpKind::Sub + | hir::BinOpKind::Div + | hir::BinOpKind::Mul + | hir::BinOpKind::Rem => Some("arithmetic operation"), + hir::BinOpKind::And | hir::BinOpKind::Or => Some("logical operation"), + hir::BinOpKind::BitXor + | hir::BinOpKind::BitAnd + | hir::BinOpKind::BitOr + | hir::BinOpKind::Shl + | hir::BinOpKind::Shr => Some("bitwise operation"), + }, + hir::ExprKind::AddrOf(..) => Some("borrow"), + hir::ExprKind::OffsetOf(..) => Some("`offset_of` call"), + hir::ExprKind::Unary(..) => Some("unary operation"), + // The `offset_of` macro wraps its contents inside a `const` block. + hir::ExprKind::ConstBlock(block) => { + let body = cx.tcx.hir_body(block.body); + if let hir::ExprKind::Block(block, _) = body.value.kind + && let Some(expr) = block.expr + && let hir::ExprKind::OffsetOf(..) = expr.kind + { + Some("`offset_of` call") + } else { + None + } + } + _ => None, + }; + + let mut op_warned = false; + + if let Some(must_use_op) = must_use_op { + let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span); + cx.emit_span_lint( + UNUSED_MUST_USE, + expr.span, + UnusedOp { + op: must_use_op, + label: expr.span, + suggestion: if expr_is_from_block { + UnusedOpSuggestion::BlockTailExpr { + before_span: span.shrink_to_lo(), + after_span: span.shrink_to_hi(), + } + } else { + UnusedOpSuggestion::NormalExpr { span: span.shrink_to_lo() } + }, + }, + ); + op_warned = true; + } + + if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { + cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); + } + + fn check_fn_must_use( + cx: &LateContext<'_>, + expr: &hir::Expr<'_>, + expr_is_from_block: bool, + ) -> bool { + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(callee, _) => { + match callee.kind { + hir::ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, callee.hir_id) { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting + _ => None, + } + } + _ => None, + } + } + hir::ExprKind::MethodCall(..) => { + cx.typeck_results().type_dependent_def_id(expr.hir_id) + } + _ => None, + }; + if let Some(def_id) = maybe_def_id { + check_must_use_def( + cx, + def_id, + expr.span, + "return value of ", + "", + expr_is_from_block, + ) + } else { + false + } + } + + /// A path through a type to a must_use source. Contains useful info for the lint. + #[derive(Debug)] + enum MustUsePath { + /// Suppress must_use checking. + Suppressed, + /// The root of the normal must_use lint with an optional message. + Def(Span, DefId, Option), + Boxed(Box), + Pinned(Box), + Opaque(Box), + TraitObject(Box), + TupleElement(Vec<(usize, Self)>), + Array(Box, u64), + /// The root of the unused_closures lint. + Closure(Span), + /// The root of the unused_coroutines lint. + Coroutine(Span), + } + + #[instrument(skip(cx, expr), level = "debug", ret)] + fn is_ty_must_use<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, + ) -> Option { + if ty.is_unit() { + return Some(MustUsePath::Suppressed); + } + let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); + let is_uninhabited = + |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); + if is_uninhabited(ty) { + return Some(MustUsePath::Suppressed); + } + + match *ty.kind() { + ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { + is_ty_must_use(cx, boxed, expr, span) + .map(|inner| MustUsePath::Boxed(Box::new(inner))) + } + ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { + let pinned_ty = args.type_at(0); + is_ty_must_use(cx, pinned_ty, expr, span) + .map(|inner| MustUsePath::Pinned(Box::new(inner))) + } + // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). + ty::Adt(def, args) + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) + && args.type_at(0).is_unit() + && is_uninhabited(args.type_at(1)) => + { + Some(MustUsePath::Suppressed) + } + // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). + ty::Adt(def, args) + if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) + && args.type_at(1).is_unit() + && is_uninhabited(args.type_at(0)) => + { + Some(MustUsePath::Suppressed) + } + ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), + ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { + elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = + pred.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) + } + ty::Dynamic(binders, _) => binders.iter().find_map(|predicate| { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() + { + let def_id = trait_ref.def_id; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::TraitObject(Box::new(inner))) + } else { + None + } + }), + ty::Tuple(tys) => { + let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { + debug_assert_eq!(elem_exprs.len(), tys.len()); + elem_exprs + } else { + &[] + }; + + // Default to `expr`. + let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); + + let nested_must_use = tys + .iter() + .zip(elem_exprs) + .enumerate() + .filter_map(|(i, (ty, expr))| { + is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) + }) + .collect::>(); + + if !nested_must_use.is_empty() { + Some(MustUsePath::TupleElement(nested_must_use)) + } else { + None + } + } + ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { + // If the array is empty we don't lint, to avoid false positives + Some(0) | None => None, + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(len) => is_ty_must_use(cx, ty, expr, span) + .map(|inner| MustUsePath::Array(Box::new(inner), len)), + }, + ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), + ty::Coroutine(def_id, ..) => { + // async fn should be treated as "implementor of `Future`" + let must_use = if cx.tcx.coroutine_is_async(def_id) { + let def_id = cx.tcx.lang_items().future_trait()?; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) + } else { + None + }; + must_use.or(Some(MustUsePath::Coroutine(span))) + } + _ => None, + } + } + + fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { + if let Some(reason) = find_attr!( + cx.tcx, def_id, + MustUse { reason, .. } => reason + ) { + // check for #[must_use = "..."] + Some(MustUsePath::Def(span, def_id, *reason)) + } else { + None + } + } + + // Returns whether further errors should be suppressed because either a lint has been + // emitted or the type should be ignored. + fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + expr_is_from_block: bool, + ) -> bool { + is_def_must_use(cx, def_id, span) + .map(|must_use_path| { + emit_must_use_untranslated( + cx, + &must_use_path, + descr_pre_path, + descr_post_path, + 1, + false, + expr_is_from_block, + ) + }) + .is_some() + } + + #[instrument(skip(cx), level = "debug")] + fn emit_must_use_untranslated( + cx: &LateContext<'_>, + path: &MustUsePath, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + is_inner: bool, + expr_is_from_block: bool, + ) { + let plural_suffix = pluralize!(plural_len); + + match path { + MustUsePath::Suppressed => {} + MustUsePath::Boxed(path) => { + let descr_pre = &format!("{descr_pre}boxed "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Pinned(path) => { + let descr_pre = &format!("{descr_pre}pinned "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::Opaque(path) => { + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TraitObject(path) => { + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TupleElement(elems) => { + for (index, path) in elems { + let descr_post = &format!(" in tuple element {index}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + } + MustUsePath::Array(path, len) => { + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), + true, + expr_is_from_block, + ); + } + MustUsePath::Closure(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Coroutine(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Def(span, def_id, reason) => { + let ancenstor_span = span.find_ancestor_not_from_macro().unwrap_or(*span); + let is_redundant_let_ignore = cx + .sess() + .source_map() + .span_to_prev_source(ancenstor_span) + .ok() + .map(|prev| prev.trim_end().ends_with("let _ =")) + .unwrap_or(false); + let suggestion_span = + if is_redundant_let_ignore { *span } else { ancenstor_span }; + cx.emit_span_lint( + UNUSED_MUST_USE, + ancenstor_span, + UnusedDef { + pre: descr_pre, + post: descr_post, + cx, + def_id: *def_id, + note: *reason, + suggestion: (!is_inner).then_some(if expr_is_from_block { + UnusedDefSuggestion::BlockTailExpr { + before_span: suggestion_span.shrink_to_lo(), + after_span: suggestion_span.shrink_to_hi(), + } + } else { + UnusedDefSuggestion::NormalExpr { + span: suggestion_span.shrink_to_lo(), + } + }), + }, + ); + } + } + } + } +} From c6ae0a9936da1818afb710815673020091e11ed3 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 14:47:38 +0100 Subject: [PATCH 04/15] make `is_ty_must_use` public --- compiler/rustc_lint/src/unused/must_use.rs | 657 ++++++++++----------- 1 file changed, 320 insertions(+), 337 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index e80e6412dae86..649fd3ce85204 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -87,6 +87,143 @@ declare_lint! { declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); +/// A path through a type to a must_use source. Contains useful info for the lint. +#[derive(Debug)] +pub enum MustUsePath { + /// Suppress must_use checking. + Suppressed, + /// The root of the normal must_use lint with an optional message. + Def(Span, DefId, Option), + Boxed(Box), + Pinned(Box), + Opaque(Box), + TraitObject(Box), + TupleElement(Vec<(usize, Self)>), + Array(Box, u64), + /// The root of the unused_closures lint. + Closure(Span), + /// The root of the unused_coroutines lint. + Coroutine(Span), +} + +#[instrument(skip(cx, expr), level = "debug", ret)] +pub fn is_ty_must_use<'tcx>( + cx: &LateContext<'tcx>, + ty: Ty<'tcx>, + expr: &hir::Expr<'_>, + span: Span, +) -> Option { + if ty.is_unit() { + return Some(MustUsePath::Suppressed); + } + let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); + let is_uninhabited = + |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); + if is_uninhabited(ty) { + return Some(MustUsePath::Suppressed); + } + + match *ty.kind() { + ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { + is_ty_must_use(cx, boxed, expr, span).map(|inner| MustUsePath::Boxed(Box::new(inner))) + } + ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { + let pinned_ty = args.type_at(0); + is_ty_must_use(cx, pinned_ty, expr, span) + .map(|inner| MustUsePath::Pinned(Box::new(inner))) + } + // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). + ty::Adt(def, args) + if cx.tcx.is_diagnostic_item(sym::Result, def.did()) + && args.type_at(0).is_unit() + && is_uninhabited(args.type_at(1)) => + { + Some(MustUsePath::Suppressed) + } + // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). + ty::Adt(def, args) + if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) + && args.type_at(1).is_unit() + && is_uninhabited(args.type_at(0)) => + { + Some(MustUsePath::Suppressed) + } + ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), + ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { + elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) + // We only care about self bounds for the impl-trait + .filter_only_self() + .find_map(|(pred, _span)| { + // We only look at the `DefId`, so it is safe to skip the binder here. + if let ty::ClauseKind::Trait(ref poly_trait_predicate) = + pred.kind().skip_binder() + { + let def_id = poly_trait_predicate.trait_ref.def_id; + + is_def_must_use(cx, def_id, span) + } else { + None + } + }) + .map(|inner| MustUsePath::Opaque(Box::new(inner))) + } + ty::Dynamic(binders, _) => binders.iter().find_map(|predicate| { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + let def_id = trait_ref.def_id; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::TraitObject(Box::new(inner))) + } else { + None + } + }), + ty::Tuple(tys) => { + let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { + debug_assert_eq!(elem_exprs.len(), tys.len()); + elem_exprs + } else { + &[] + }; + + // Default to `expr`. + let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); + + let nested_must_use = tys + .iter() + .zip(elem_exprs) + .enumerate() + .filter_map(|(i, (ty, expr))| { + is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) + }) + .collect::>(); + + if !nested_must_use.is_empty() { + Some(MustUsePath::TupleElement(nested_must_use)) + } else { + None + } + } + ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { + // If the array is empty we don't lint, to avoid false positives + Some(0) | None => None, + // If the array is definitely non-empty, we can do `#[must_use]` checking. + Some(len) => is_ty_must_use(cx, ty, expr, span) + .map(|inner| MustUsePath::Array(Box::new(inner), len)), + }, + ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), + ty::Coroutine(def_id, ..) => { + // async fn should be treated as "implementor of `Future`" + let must_use = if cx.tcx.coroutine_is_async(def_id) { + let def_id = cx.tcx.lang_items().future_trait()?; + is_def_must_use(cx, def_id, span).map(|inner| MustUsePath::Opaque(Box::new(inner))) + } else { + None + }; + must_use.or(Some(MustUsePath::Coroutine(span))) + } + _ => None, + } +} + impl<'tcx> LateLintPass<'tcx> for UnusedResults { fn check_stmt(&mut self, cx: &LateContext<'_>, s: &hir::Stmt<'_>) { let hir::StmtKind::Semi(mut expr) = s.kind else { @@ -213,358 +350,204 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); } + } +} - fn check_fn_must_use( - cx: &LateContext<'_>, - expr: &hir::Expr<'_>, - expr_is_from_block: bool, - ) -> bool { - let maybe_def_id = match expr.kind { - hir::ExprKind::Call(callee, _) => { - match callee.kind { - hir::ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, callee.hir_id) { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), - // `Res::Local` if it was a closure, for which we - // do not currently support must-use linting - _ => None, - } - } +/// Checks if `expr` is a \[method\] call expression marked as `#[must_use]` and emits a lint if so. +/// Returns `true` if the lint has been emitted. +fn check_fn_must_use(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expr_is_from_block: bool) -> bool { + let maybe_def_id = match expr.kind { + hir::ExprKind::Call(callee, _) => { + match callee.kind { + hir::ExprKind::Path(ref qpath) => { + match cx.qpath_res(qpath, callee.hir_id) { + Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting _ => None, } } - hir::ExprKind::MethodCall(..) => { - cx.typeck_results().type_dependent_def_id(expr.hir_id) - } _ => None, - }; - if let Some(def_id) = maybe_def_id { - check_must_use_def( - cx, - def_id, - expr.span, - "return value of ", - "", - expr_is_from_block, - ) - } else { - false } } + hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), + _ => None, + }; + if let Some(def_id) = maybe_def_id { + check_must_use_def(cx, def_id, expr.span, "return value of ", "", expr_is_from_block) + } else { + false + } +} - /// A path through a type to a must_use source. Contains useful info for the lint. - #[derive(Debug)] - enum MustUsePath { - /// Suppress must_use checking. - Suppressed, - /// The root of the normal must_use lint with an optional message. - Def(Span, DefId, Option), - Boxed(Box), - Pinned(Box), - Opaque(Box), - TraitObject(Box), - TupleElement(Vec<(usize, Self)>), - Array(Box, u64), - /// The root of the unused_closures lint. - Closure(Span), - /// The root of the unused_coroutines lint. - Coroutine(Span), - } - - #[instrument(skip(cx, expr), level = "debug", ret)] - fn is_ty_must_use<'tcx>( - cx: &LateContext<'tcx>, - ty: Ty<'tcx>, - expr: &hir::Expr<'_>, - span: Span, - ) -> Option { - if ty.is_unit() { - return Some(MustUsePath::Suppressed); - } - let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); - let is_uninhabited = - |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); - if is_uninhabited(ty) { - return Some(MustUsePath::Suppressed); - } - - match *ty.kind() { - ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, span) - .map(|inner| MustUsePath::Boxed(Box::new(inner))) - } - ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { - let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, span) - .map(|inner| MustUsePath::Pinned(Box::new(inner))) - } - // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::Result, def.did()) - && args.type_at(0).is_unit() - && is_uninhabited(args.type_at(1)) => - { - Some(MustUsePath::Suppressed) - } - // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). - ty::Adt(def, args) - if cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) - && args.type_at(1).is_unit() - && is_uninhabited(args.type_at(0)) => - { - Some(MustUsePath::Suppressed) - } - ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), - ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { - elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) - // We only care about self bounds for the impl-trait - .filter_only_self() - .find_map(|(pred, _span)| { - // We only look at the `DefId`, so it is safe to skip the binder here. - if let ty::ClauseKind::Trait(ref poly_trait_predicate) = - pred.kind().skip_binder() - { - let def_id = poly_trait_predicate.trait_ref.def_id; - - is_def_must_use(cx, def_id, span) - } else { - None - } - }) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } - ty::Dynamic(binders, _) => binders.iter().find_map(|predicate| { - if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() - { - let def_id = trait_ref.def_id; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::TraitObject(Box::new(inner))) - } else { - None - } - }), - ty::Tuple(tys) => { - let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { - debug_assert_eq!(elem_exprs.len(), tys.len()); - elem_exprs - } else { - &[] - }; +fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { + if let Some(reason) = find_attr!( + cx.tcx, def_id, + MustUse { reason, .. } => reason + ) { + // check for #[must_use = "..."] + Some(MustUsePath::Def(span, def_id, *reason)) + } else { + None + } +} - // Default to `expr`. - let elem_exprs = elem_exprs.iter().chain(iter::repeat(expr)); +// Returns whether further errors should be suppressed because either a lint has been +// emitted or the type should be ignored. +fn check_must_use_def( + cx: &LateContext<'_>, + def_id: DefId, + span: Span, + descr_pre_path: &str, + descr_post_path: &str, + expr_is_from_block: bool, +) -> bool { + is_def_must_use(cx, def_id, span) + .map(|must_use_path| { + emit_must_use_untranslated( + cx, + &must_use_path, + descr_pre_path, + descr_post_path, + 1, + false, + expr_is_from_block, + ) + }) + .is_some() +} - let nested_must_use = tys - .iter() - .zip(elem_exprs) - .enumerate() - .filter_map(|(i, (ty, expr))| { - is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) - }) - .collect::>(); +#[instrument(skip(cx), level = "debug")] +fn emit_must_use_untranslated( + cx: &LateContext<'_>, + path: &MustUsePath, + descr_pre: &str, + descr_post: &str, + plural_len: usize, + is_inner: bool, + expr_is_from_block: bool, +) { + let plural_suffix = pluralize!(plural_len); - if !nested_must_use.is_empty() { - Some(MustUsePath::TupleElement(nested_must_use)) - } else { - None - } - } - ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { - // If the array is empty we don't lint, to avoid false positives - Some(0) | None => None, - // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, span) - .map(|inner| MustUsePath::Array(Box::new(inner), len)), - }, - ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), - ty::Coroutine(def_id, ..) => { - // async fn should be treated as "implementor of `Future`" - let must_use = if cx.tcx.coroutine_is_async(def_id) { - let def_id = cx.tcx.lang_items().future_trait()?; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::Opaque(Box::new(inner))) - } else { - None - }; - must_use.or(Some(MustUsePath::Coroutine(span))) - } - _ => None, - } + match path { + MustUsePath::Suppressed => {} + MustUsePath::Boxed(path) => { + let descr_pre = &format!("{descr_pre}boxed "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } - - fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { - if let Some(reason) = find_attr!( - cx.tcx, def_id, - MustUse { reason, .. } => reason - ) { - // check for #[must_use = "..."] - Some(MustUsePath::Def(span, def_id, *reason)) - } else { - None - } + MustUsePath::Pinned(path) => { + let descr_pre = &format!("{descr_pre}pinned "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } - - // Returns whether further errors should be suppressed because either a lint has been - // emitted or the type should be ignored. - fn check_must_use_def( - cx: &LateContext<'_>, - def_id: DefId, - span: Span, - descr_pre_path: &str, - descr_post_path: &str, - expr_is_from_block: bool, - ) -> bool { - is_def_must_use(cx, def_id, span) - .map(|must_use_path| { - emit_must_use_untranslated( - cx, - &must_use_path, - descr_pre_path, - descr_post_path, - 1, - false, - expr_is_from_block, - ) - }) - .is_some() + MustUsePath::Opaque(path) => { + let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } - - #[instrument(skip(cx), level = "debug")] - fn emit_must_use_untranslated( - cx: &LateContext<'_>, - path: &MustUsePath, - descr_pre: &str, - descr_post: &str, - plural_len: usize, - is_inner: bool, - expr_is_from_block: bool, - ) { - let plural_suffix = pluralize!(plural_len); - - match path { - MustUsePath::Suppressed => {} - MustUsePath::Boxed(path) => { - let descr_pre = &format!("{descr_pre}boxed "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Pinned(path) => { - let descr_pre = &format!("{descr_pre}pinned "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::Opaque(path) => { - let descr_pre = &format!("{descr_pre}implementer{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TraitObject(path) => { - let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - MustUsePath::TupleElement(elems) => { - for (index, path) in elems { - let descr_post = &format!(" in tuple element {index}"); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len, - true, - expr_is_from_block, - ); - } - } - MustUsePath::Array(path, len) => { - let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); - emit_must_use_untranslated( - cx, - path, - descr_pre, - descr_post, - plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), - true, - expr_is_from_block, - ); - } - MustUsePath::Closure(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Coroutine(span) => { - cx.emit_span_lint( - UNUSED_MUST_USE, - *span, - UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, - ); - } - MustUsePath::Def(span, def_id, reason) => { - let ancenstor_span = span.find_ancestor_not_from_macro().unwrap_or(*span); - let is_redundant_let_ignore = cx - .sess() - .source_map() - .span_to_prev_source(ancenstor_span) - .ok() - .map(|prev| prev.trim_end().ends_with("let _ =")) - .unwrap_or(false); - let suggestion_span = - if is_redundant_let_ignore { *span } else { ancenstor_span }; - cx.emit_span_lint( - UNUSED_MUST_USE, - ancenstor_span, - UnusedDef { - pre: descr_pre, - post: descr_post, - cx, - def_id: *def_id, - note: *reason, - suggestion: (!is_inner).then_some(if expr_is_from_block { - UnusedDefSuggestion::BlockTailExpr { - before_span: suggestion_span.shrink_to_lo(), - after_span: suggestion_span.shrink_to_hi(), - } - } else { - UnusedDefSuggestion::NormalExpr { - span: suggestion_span.shrink_to_lo(), - } - }), - }, - ); - } + MustUsePath::TraitObject(path) => { + let descr_post = &format!(" trait object{plural_suffix}{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::TupleElement(elems) => { + for (index, path) in elems { + let descr_post = &format!(" in tuple element {index}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); } } + MustUsePath::Array(path, len) => { + let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len.saturating_add(usize::try_from(*len).unwrap_or(usize::MAX)), + true, + expr_is_from_block, + ); + } + MustUsePath::Closure(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedClosure { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Coroutine(span) => { + cx.emit_span_lint( + UNUSED_MUST_USE, + *span, + UnusedCoroutine { count: plural_len, pre: descr_pre, post: descr_post }, + ); + } + MustUsePath::Def(span, def_id, reason) => { + let ancenstor_span = span.find_ancestor_not_from_macro().unwrap_or(*span); + let is_redundant_let_ignore = cx + .sess() + .source_map() + .span_to_prev_source(ancenstor_span) + .ok() + .map(|prev| prev.trim_end().ends_with("let _ =")) + .unwrap_or(false); + let suggestion_span = if is_redundant_let_ignore { *span } else { ancenstor_span }; + cx.emit_span_lint( + UNUSED_MUST_USE, + ancenstor_span, + UnusedDef { + pre: descr_pre, + post: descr_post, + cx, + def_id: *def_id, + note: *reason, + suggestion: (!is_inner).then_some(if expr_is_from_block { + UnusedDefSuggestion::BlockTailExpr { + before_span: suggestion_span.shrink_to_lo(), + after_span: suggestion_span.shrink_to_hi(), + } + } else { + UnusedDefSuggestion::NormalExpr { span: suggestion_span.shrink_to_lo() } + }), + }, + ); + } } } From 696a105500ea371a13919eb8fef3d8ae48ac5792 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 14:47:38 +0100 Subject: [PATCH 05/15] `must_use`: internal doc improvements --- compiler/rustc_lint/src/unused/must_use.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index 649fd3ce85204..351316a3e5079 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -87,12 +87,12 @@ declare_lint! { declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); -/// A path through a type to a must_use source. Contains useful info for the lint. +/// A path through a type to a `must_use` source. Contains useful info for the lint. #[derive(Debug)] pub enum MustUsePath { /// Suppress must_use checking. Suppressed, - /// The root of the normal must_use lint with an optional message. + /// The root of the normal `must_use` lint with an optional message. Def(Span, DefId, Option), Boxed(Box), Pinned(Box), @@ -106,6 +106,8 @@ pub enum MustUsePath { Coroutine(Span), } +/// Returns `Some(path)` if `ty` should be considered as "`must_use`" in the context of `expr` +/// (`expr` is used to get the parent module, which can affect which types are considered uninhabited). #[instrument(skip(cx, expr), level = "debug", ret)] pub fn is_ty_must_use<'tcx>( cx: &LateContext<'tcx>, @@ -392,8 +394,7 @@ fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option, def_id: DefId, From b2cc4d57a62d63aeac699774b6d545bdbbdbc865 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 15:18:46 +0100 Subject: [PATCH 06/15] `must_use`: make the check for trivial types cleaner This removes `Suppressed` as a must use cause that could be nested in another (we did not ever return this, but now it's encoded in the types). I also renamed Suppressed->Trvial and added some docs to clear confusion. --- compiler/rustc_lint/src/unused/must_use.rs | 108 ++++++++++++++------- 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index 351316a3e5079..e3d00afc03a6f 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -87,11 +87,39 @@ declare_lint! { declare_lint_pass!(UnusedResults => [UNUSED_MUST_USE, UNUSED_RESULTS]); +/// Must the type be used? +#[derive(Debug)] +pub enum IsTyMustUse { + /// Yes, `MustUsePath` contains an explanation for why the type must be used. + /// This will result in `unused_must_use` lint. + Yes(MustUsePath), + /// No, an ordinary type that may be ignored. + /// This will result in `unused_results` lint. + No, + /// No, the type is trivial and thus should always be ignored. + /// (this suppresses `unused_results` lint) + Trivial, +} + +impl IsTyMustUse { + fn map(self, f: impl FnOnce(MustUsePath) -> MustUsePath) -> Self { + match self { + Self::Yes(must_use_path) => Self::Yes(f(must_use_path)), + _ => self, + } + } + + fn yes(self) -> Option { + match self { + Self::Yes(must_use_path) => Some(must_use_path), + _ => None, + } + } +} + /// A path through a type to a `must_use` source. Contains useful info for the lint. #[derive(Debug)] pub enum MustUsePath { - /// Suppress must_use checking. - Suppressed, /// The root of the normal `must_use` lint with an optional message. Def(Span, DefId, Option), Boxed(Box), @@ -114,15 +142,15 @@ pub fn is_ty_must_use<'tcx>( ty: Ty<'tcx>, expr: &hir::Expr<'_>, span: Span, -) -> Option { +) -> IsTyMustUse { if ty.is_unit() { - return Some(MustUsePath::Suppressed); + return IsTyMustUse::Trivial; } let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); let is_uninhabited = |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); if is_uninhabited(ty) { - return Some(MustUsePath::Suppressed); + return IsTyMustUse::Trivial; } match *ty.kind() { @@ -140,7 +168,7 @@ pub fn is_ty_must_use<'tcx>( && args.type_at(0).is_unit() && is_uninhabited(args.type_at(1)) => { - Some(MustUsePath::Suppressed) + IsTyMustUse::Trivial } // Suppress warnings on `ControlFlow` (e.g. `ControlFlow`). ty::Adt(def, args) @@ -148,9 +176,11 @@ pub fn is_ty_must_use<'tcx>( && args.type_at(1).is_unit() && is_uninhabited(args.type_at(0)) => { - Some(MustUsePath::Suppressed) + IsTyMustUse::Trivial + } + ty::Adt(def, _) => { + is_def_must_use(cx, def.did(), span).map_or(IsTyMustUse::No, IsTyMustUse::Yes) } - ty::Adt(def, _) => is_def_must_use(cx, def.did(), span), ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { elaborate(cx.tcx, cx.tcx.explicit_item_self_bounds(def).iter_identity_copied()) // We only care about self bounds for the impl-trait @@ -168,16 +198,20 @@ pub fn is_ty_must_use<'tcx>( } }) .map(|inner| MustUsePath::Opaque(Box::new(inner))) + .map_or(IsTyMustUse::No, IsTyMustUse::Yes) } - ty::Dynamic(binders, _) => binders.iter().find_map(|predicate| { - if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { - let def_id = trait_ref.def_id; - is_def_must_use(cx, def_id, span) - .map(|inner| MustUsePath::TraitObject(Box::new(inner))) - } else { - None - } - }), + ty::Dynamic(binders, _) => binders + .iter() + .find_map(|predicate| { + if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() { + let def_id = trait_ref.def_id; + is_def_must_use(cx, def_id, span) + .map(|inner| MustUsePath::TraitObject(Box::new(inner))) + } else { + None + } + }) + .map_or(IsTyMustUse::No, IsTyMustUse::Yes), ty::Tuple(tys) => { let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { debug_assert_eq!(elem_exprs.len(), tys.len()); @@ -194,35 +228,38 @@ pub fn is_ty_must_use<'tcx>( .zip(elem_exprs) .enumerate() .filter_map(|(i, (ty, expr))| { - is_ty_must_use(cx, ty, expr, expr.span).map(|path| (i, path)) + is_ty_must_use(cx, ty, expr, expr.span).yes().map(|path| (i, path)) }) .collect::>(); if !nested_must_use.is_empty() { - Some(MustUsePath::TupleElement(nested_must_use)) + IsTyMustUse::Yes(MustUsePath::TupleElement(nested_must_use)) } else { - None + IsTyMustUse::No } } ty::Array(ty, len) => match len.try_to_target_usize(cx.tcx) { // If the array is empty we don't lint, to avoid false positives - Some(0) | None => None, + Some(0) | None => IsTyMustUse::No, // If the array is definitely non-empty, we can do `#[must_use]` checking. Some(len) => is_ty_must_use(cx, ty, expr, span) .map(|inner| MustUsePath::Array(Box::new(inner), len)), }, - ty::Closure(..) | ty::CoroutineClosure(..) => Some(MustUsePath::Closure(span)), + ty::Closure(..) | ty::CoroutineClosure(..) => IsTyMustUse::Yes(MustUsePath::Closure(span)), ty::Coroutine(def_id, ..) => { // async fn should be treated as "implementor of `Future`" - let must_use = if cx.tcx.coroutine_is_async(def_id) { - let def_id = cx.tcx.lang_items().future_trait()?; - is_def_must_use(cx, def_id, span).map(|inner| MustUsePath::Opaque(Box::new(inner))) + if cx.tcx.coroutine_is_async(def_id) + && let Some(def_id) = cx.tcx.lang_items().future_trait() + { + IsTyMustUse::Yes(MustUsePath::Opaque(Box::new( + is_def_must_use(cx, def_id, span) + .expect("future trait is marked as `#[must_use]`"), + ))) } else { - None - }; - must_use.or(Some(MustUsePath::Coroutine(span))) + IsTyMustUse::Yes(MustUsePath::Coroutine(span)) + } } - _ => None, + _ => IsTyMustUse::No, } } @@ -269,17 +306,18 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let ty = cx.typeck_results().expr_ty(expr); let must_use_result = is_ty_must_use(cx, ty, expr, expr.span); - let type_lint_emitted_or_suppressed = match must_use_result { - Some(path) => { + let type_lint_emitted_or_trivial = match must_use_result { + IsTyMustUse::Yes(path) => { emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); true } - None => false, + IsTyMustUse::Trivial => true, + IsTyMustUse::No => false, }; let fn_warned = check_fn_must_use(cx, expr, expr_is_from_block); - if !fn_warned && type_lint_emitted_or_suppressed { + if !fn_warned && type_lint_emitted_or_trivial { // We don't warn about unused unit or uninhabited types. // (See https://github.com/rust-lang/rust/issues/43806 for details.) return; @@ -349,7 +387,8 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { op_warned = true; } - if !(type_lint_emitted_or_suppressed || fn_warned || op_warned) { + // Only emit unused results lint if we haven't emitted any of the more specific lints and the expression type is non trivial. + if !(type_lint_emitted_or_trivial || fn_warned || op_warned) { cx.emit_span_lint(UNUSED_RESULTS, s.span, UnusedResult { ty }); } } @@ -431,7 +470,6 @@ fn emit_must_use_untranslated( let plural_suffix = pluralize!(plural_len); match path { - MustUsePath::Suppressed => {} MustUsePath::Boxed(path) => { let descr_pre = &format!("{descr_pre}boxed "); emit_must_use_untranslated( From ae1e2c6b277d761aa05c19cd84017251a1cb2d82 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 15:18:46 +0100 Subject: [PATCH 07/15] `must_use`: drive-by-cleanup --- compiler/rustc_lint/src/unused/must_use.rs | 90 +++++++++++----------- 1 file changed, 43 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index e3d00afc03a6f..0309a2c95c656 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -146,14 +146,13 @@ pub fn is_ty_must_use<'tcx>( if ty.is_unit() { return IsTyMustUse::Trivial; } + let parent_mod_did = cx.tcx.parent_module(expr.hir_id).to_def_id(); let is_uninhabited = |t: Ty<'tcx>| !t.is_inhabited_from(cx.tcx, parent_mod_did, cx.typing_env()); - if is_uninhabited(ty) { - return IsTyMustUse::Trivial; - } match *ty.kind() { + _ if is_uninhabited(ty) => IsTyMustUse::Trivial, ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { is_ty_must_use(cx, boxed, expr, span).map(|inner| MustUsePath::Boxed(Box::new(inner))) } @@ -212,6 +211,7 @@ pub fn is_ty_must_use<'tcx>( } }) .map_or(IsTyMustUse::No, IsTyMustUse::Yes), + // NB: unit is checked up above; this is only reachable for tuples with at least one element ty::Tuple(tys) => { let elem_exprs = if let hir::ExprKind::Tup(elem_exprs) = expr.kind { debug_assert_eq!(elem_exprs.len(), tys.len()); @@ -364,28 +364,29 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { _ => None, }; - let mut op_warned = false; - - if let Some(must_use_op) = must_use_op { - let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span); - cx.emit_span_lint( - UNUSED_MUST_USE, - expr.span, - UnusedOp { - op: must_use_op, - label: expr.span, - suggestion: if expr_is_from_block { - UnusedOpSuggestion::BlockTailExpr { - before_span: span.shrink_to_lo(), - after_span: span.shrink_to_hi(), - } - } else { - UnusedOpSuggestion::NormalExpr { span: span.shrink_to_lo() } + let op_warned = match must_use_op { + Some(must_use_op) => { + let span = expr.span.find_ancestor_not_from_macro().unwrap_or(expr.span); + cx.emit_span_lint( + UNUSED_MUST_USE, + expr.span, + UnusedOp { + op: must_use_op, + label: expr.span, + suggestion: if expr_is_from_block { + UnusedOpSuggestion::BlockTailExpr { + before_span: span.shrink_to_lo(), + after_span: span.shrink_to_hi(), + } + } else { + UnusedOpSuggestion::NormalExpr { span: span.shrink_to_lo() } + }, }, - }, - ); - op_warned = true; - } + ); + true + } + None => false, + }; // Only emit unused results lint if we haven't emitted any of the more specific lints and the expression type is non trivial. if !(type_lint_emitted_or_trivial || fn_warned || op_warned) { @@ -399,38 +400,33 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { fn check_fn_must_use(cx: &LateContext<'_>, expr: &hir::Expr<'_>, expr_is_from_block: bool) -> bool { let maybe_def_id = match expr.kind { hir::ExprKind::Call(callee, _) => { - match callee.kind { - hir::ExprKind::Path(ref qpath) => { - match cx.qpath_res(qpath, callee.hir_id) { - Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) => Some(def_id), - // `Res::Local` if it was a closure, for which we - // do not currently support must-use linting - _ => None, - } - } - _ => None, + if let hir::ExprKind::Path(ref qpath) = callee.kind + // `Res::Local` if it was a closure, for which we + // do not currently support must-use linting + && let Res::Def(DefKind::Fn | DefKind::AssocFn, def_id) = + cx.qpath_res(qpath, callee.hir_id) + { + Some(def_id) + } else { + None } } hir::ExprKind::MethodCall(..) => cx.typeck_results().type_dependent_def_id(expr.hir_id), _ => None, }; - if let Some(def_id) = maybe_def_id { - check_must_use_def(cx, def_id, expr.span, "return value of ", "", expr_is_from_block) - } else { - false + + match maybe_def_id { + Some(def_id) => { + check_must_use_def(cx, def_id, expr.span, "return value of ", "", expr_is_from_block) + } + None => false, } } fn is_def_must_use(cx: &LateContext<'_>, def_id: DefId, span: Span) -> Option { - if let Some(reason) = find_attr!( - cx.tcx, def_id, - MustUse { reason, .. } => reason - ) { - // check for #[must_use = "..."] - Some(MustUsePath::Def(span, def_id, *reason)) - } else { - None - } + // check for #[must_use = "..."] + find_attr!(cx.tcx, def_id, MustUse { reason, .. } => reason) + .map(|reason| MustUsePath::Def(span, def_id, *reason)) } /// Returns whether further errors should be suppressed because a lint has been emitted. From 1b97e9bdc35d7d2a36d695580bc09b210635d0e3 Mon Sep 17 00:00:00 2001 From: Waffle Lapkin Date: Mon, 23 Feb 2026 16:08:40 +0100 Subject: [PATCH 08/15] add a flag to `is_ty_must_use` to simplify `Result` and `ControlFlow` --- compiler/rustc_lint/src/unused/must_use.rs | 68 ++++++++++++++++++++-- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_lint/src/unused/must_use.rs b/compiler/rustc_lint/src/unused/must_use.rs index 0309a2c95c656..f2d621c2ad5ed 100644 --- a/compiler/rustc_lint/src/unused/must_use.rs +++ b/compiler/rustc_lint/src/unused/must_use.rs @@ -127,6 +127,10 @@ pub enum MustUsePath { Opaque(Box), TraitObject(Box), TupleElement(Vec<(usize, Self)>), + /// `Result` + Result(Box), + /// `ControlFlow` + ControlFlow(Box), Array(Box, u64), /// The root of the unused_closures lint. Closure(Span), @@ -136,12 +140,19 @@ pub enum MustUsePath { /// Returns `Some(path)` if `ty` should be considered as "`must_use`" in the context of `expr` /// (`expr` is used to get the parent module, which can affect which types are considered uninhabited). +/// +/// If `simplify_uninhabited` is true, this function considers `Result` and +/// `ControlFlow` the same as `T` (we don't set this *yet* in rustc, but expose this +/// so clippy can use this). +// +// FIXME: remove `simplify_uninhabited` once clippy had a release with the new semantics. #[instrument(skip(cx, expr), level = "debug", ret)] pub fn is_ty_must_use<'tcx>( cx: &LateContext<'tcx>, ty: Ty<'tcx>, expr: &hir::Expr<'_>, span: Span, + simplify_uninhabited: bool, ) -> IsTyMustUse { if ty.is_unit() { return IsTyMustUse::Trivial; @@ -154,13 +165,34 @@ pub fn is_ty_must_use<'tcx>( match *ty.kind() { _ if is_uninhabited(ty) => IsTyMustUse::Trivial, ty::Adt(..) if let Some(boxed) = ty.boxed_ty() => { - is_ty_must_use(cx, boxed, expr, span).map(|inner| MustUsePath::Boxed(Box::new(inner))) + is_ty_must_use(cx, boxed, expr, span, simplify_uninhabited) + .map(|inner| MustUsePath::Boxed(Box::new(inner))) } ty::Adt(def, args) if cx.tcx.is_lang_item(def.did(), LangItem::Pin) => { let pinned_ty = args.type_at(0); - is_ty_must_use(cx, pinned_ty, expr, span) + is_ty_must_use(cx, pinned_ty, expr, span, simplify_uninhabited) .map(|inner| MustUsePath::Pinned(Box::new(inner))) } + // Consider `Result` (e.g. `Result<(), !>`) equivalent to `T`. + ty::Adt(def, args) + if simplify_uninhabited + && cx.tcx.is_diagnostic_item(sym::Result, def.did()) + && is_uninhabited(args.type_at(1)) => + { + let ok_ty = args.type_at(0); + is_ty_must_use(cx, ok_ty, expr, span, simplify_uninhabited) + .map(|path| MustUsePath::Result(Box::new(path))) + } + // Consider `ControlFlow` (e.g. `ControlFlow`) equivalent to `T`. + ty::Adt(def, args) + if simplify_uninhabited + && cx.tcx.is_diagnostic_item(sym::ControlFlow, def.did()) + && is_uninhabited(args.type_at(0)) => + { + let continue_ty = args.type_at(1); + is_ty_must_use(cx, continue_ty, expr, span, simplify_uninhabited) + .map(|path| MustUsePath::ControlFlow(Box::new(path))) + } // Suppress warnings on `Result<(), Uninhabited>` (e.g. `Result<(), !>`). ty::Adt(def, args) if cx.tcx.is_diagnostic_item(sym::Result, def.did()) @@ -228,7 +260,9 @@ pub fn is_ty_must_use<'tcx>( .zip(elem_exprs) .enumerate() .filter_map(|(i, (ty, expr))| { - is_ty_must_use(cx, ty, expr, expr.span).yes().map(|path| (i, path)) + is_ty_must_use(cx, ty, expr, expr.span, simplify_uninhabited) + .yes() + .map(|path| (i, path)) }) .collect::>(); @@ -242,7 +276,7 @@ pub fn is_ty_must_use<'tcx>( // If the array is empty we don't lint, to avoid false positives Some(0) | None => IsTyMustUse::No, // If the array is definitely non-empty, we can do `#[must_use]` checking. - Some(len) => is_ty_must_use(cx, ty, expr, span) + Some(len) => is_ty_must_use(cx, ty, expr, span, simplify_uninhabited) .map(|inner| MustUsePath::Array(Box::new(inner), len)), }, ty::Closure(..) | ty::CoroutineClosure(..) => IsTyMustUse::Yes(MustUsePath::Closure(span)), @@ -305,7 +339,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { let ty = cx.typeck_results().expr_ty(expr); - let must_use_result = is_ty_must_use(cx, ty, expr, expr.span); + let must_use_result = is_ty_must_use(cx, ty, expr, expr.span, false); let type_lint_emitted_or_trivial = match must_use_result { IsTyMustUse::Yes(path) => { emit_must_use_untranslated(cx, &path, "", "", 1, false, expr_is_from_block); @@ -528,6 +562,30 @@ fn emit_must_use_untranslated( ); } } + MustUsePath::Result(path) => { + let descr_post = &format!(" in a `Result` with an uninhabited error{descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } + MustUsePath::ControlFlow(path) => { + let descr_post = &format!(" in a `ControlFlow` with an uninhabited break {descr_post}"); + emit_must_use_untranslated( + cx, + path, + descr_pre, + descr_post, + plural_len, + true, + expr_is_from_block, + ); + } MustUsePath::Array(path, len) => { let descr_pre = &format!("{descr_pre}array{plural_suffix} of "); emit_must_use_untranslated( From a67baaa04e5bd23728d3c65da2ece8363371beb6 Mon Sep 17 00:00:00 2001 From: rustbot <47979223+rustbot@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:01:34 +0100 Subject: [PATCH 09/15] Update books --- src/doc/embedded-book | 2 +- src/doc/reference | 2 +- src/doc/rust-by-example | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/doc/embedded-book b/src/doc/embedded-book index fe88fbb68391a..99d0341ff4e06 160000 --- a/src/doc/embedded-book +++ b/src/doc/embedded-book @@ -1 +1 @@ -Subproject commit fe88fbb68391a465680dd91109f0a151a1676f3e +Subproject commit 99d0341ff4e06757490af8fceee790c4ede50bc0 diff --git a/src/doc/reference b/src/doc/reference index addd0602c819b..442cbef910566 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit addd0602c819b6526b9cc97653b0fadca395528c +Subproject commit 442cbef9105662887d5eae2882ca551f3726bf28 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index bac931ef1673a..5383db524711c 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit bac931ef1673af63fb60c3d691633034713cca20 +Subproject commit 5383db524711c0c9c43c3ca9e5e706089672ed6a From 8ac769f9d0675d7ca28ad01396ab204d8e3a96d7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Feb 2026 14:03:16 +1100 Subject: [PATCH 10/15] Remove `rustc_feedable_queries` and `define_feedable` macros. `rustc_queries!` produces two macros: `rustc_with_all_queries` and `rustc_feedable_queries`. The latter is similar to the former but only includes feedable queries. But feedable queries don't need a separate mechanism because we can identify feedable queries within `define_callbacks!` by just looking for the `feedable` modifier. (That's what we do with every modifier other than `feedable`.) This commit removes the special handling and treats feedable queries like everything else. Note that this commit exposes and fixes a latent doc bug. The doc comment for query `explicit_predicates_of` links to `Self::predicates_of`. `explicit_predicates_of` is a feedable query but `predicates_of` is not, so for `TyCtxtFeed` this link is invalid. This wasn't manifesting because `TyCtxtFeed` wasn't getting doc comments attached. It now is, so I changed the link to `TyCtxt::predicates_of`. --- compiler/rustc_macros/src/query.rs | 10 --- compiler/rustc_middle/src/queries.rs | 3 +- compiler/rustc_middle/src/query/plumbing.rs | 68 +++++++++++---------- 3 files changed, 36 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_macros/src/query.rs b/compiler/rustc_macros/src/query.rs index 346604a46ef7d..ebfcb50e9cde5 100644 --- a/compiler/rustc_macros/src/query.rs +++ b/compiler/rustc_macros/src/query.rs @@ -399,7 +399,6 @@ pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { let mut query_stream = quote! {}; let mut helpers = HelperTokenStreams::default(); - let mut feedable_queries = quote! {}; let mut analyzer_stream = quote! {}; let mut errors = quote! {}; @@ -480,10 +479,6 @@ pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { feedable.span(), "Query {name} cannot be both `feedable` and `eval_always`." ); - feedable_queries.extend(quote! { - [#modifiers_stream] - fn #name(#key_ty) #return_ty, - }); } add_to_analyzer_stream(&query, &mut analyzer_stream); @@ -514,11 +509,6 @@ pub(super) fn rustc_queries(input: TokenStream) -> TokenStream { } } } - macro_rules! rustc_feedable_queries { - ( $macro:ident! ) => { - $macro!(#feedable_queries); - } - } // Add hints for rust-analyzer mod _analyzer_hints { diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index 3a3a2743ec4f8..e303a8aeab917 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -825,7 +825,7 @@ rustc_queries! { /// Returns the explicitly user-written *predicates* of the definition given by `DefId` /// that must be proven true at usage sites (and which can be assumed at definition site). /// - /// You should probably use [`Self::predicates_of`] unless you're looking for + /// You should probably use [`TyCtxt::predicates_of`] unless you're looking for /// predicates with explicit spans for diagnostics purposes. query explicit_predicates_of(key: DefId) -> ty::GenericPredicates<'tcx> { desc { "computing explicit predicates of `{}`", tcx.def_path_str(key) } @@ -2780,4 +2780,3 @@ rustc_queries! { } rustc_with_all_queries! { define_callbacks! } -rustc_feedable_queries! { define_feedable! } diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 506d7616cbfc9..1319c79dbbb96 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -295,6 +295,17 @@ macro_rules! if_return_result_from_ensure_ok { }; } +// Expands to `$item` if the `feedable` modifier is present. +macro_rules! item_if_feedable { + ([] $($item:tt)*) => {}; + ([(feedable) $($rest:tt)*] $($item:tt)*) => { + $($item)* + }; + ([$other:tt $($modifiers:tt)*] $($item:tt)*) => { + item_if_feedable! { [$($modifiers)*] $($item)* } + }; +} + macro_rules! define_callbacks { ( // You might expect the key to be `$K:ty`, but it needs to be `$($K:tt)*` so that @@ -489,6 +500,30 @@ macro_rules! define_callbacks { )* } + $( + item_if_feedable! { + [$($modifiers)*] + impl<'tcx, K: $crate::query::IntoQueryParam<$name::Key<'tcx>> + Copy> + TyCtxtFeed<'tcx, K> + { + $(#[$attr])* + #[inline(always)] + pub fn $name(self, value: $name::ProvidedValue<'tcx>) { + let key = self.key().into_query_param(); + let erased_value = $name::provided_to_erased(self.tcx, value); + $crate::query::inner::query_feed( + self.tcx, + dep_graph::DepKind::$name, + &self.tcx.query_system.query_vtables.$name, + &self.tcx.query_system.caches.$name, + key, + erased_value, + ); + } + } + } + )* + /// Holds a `QueryVTable` for each query. /// /// ("Per" just makes this pluralized name more visually distinct.) @@ -578,39 +613,6 @@ macro_rules! define_callbacks { }; } -// Note: `$V` is unused but present so this can be called by `rustc_with_all_queries`. -macro_rules! define_feedable { - ( - $( - $(#[$attr:meta])* - [$($modifiers:tt)*] - fn $name:ident($K:ty) -> $V:ty, - )* - ) => { - $( - impl<'tcx, K: $crate::query::IntoQueryParam<$K> + Copy> TyCtxtFeed<'tcx, K> { - $(#[$attr])* - #[inline(always)] - pub fn $name(self, value: $name::ProvidedValue<'tcx>) { - let key = self.key().into_query_param(); - - let tcx = self.tcx; - let erased_value = $name::provided_to_erased(tcx, value); - - $crate::query::inner::query_feed( - tcx, - dep_graph::DepKind::$name, - &tcx.query_system.query_vtables.$name, - &tcx.query_system.caches.$name, - key, - erased_value, - ); - } - } - )* - } -} - // Each of these queries corresponds to a function pointer field in the // `Providers` struct for requesting a value of that type, and a method // on `tcx: TyCtxt` (and `tcx.at(span)`) for doing that request in a way From 092d4fbcd840fe9130a6a2abbd1f0a6fd89e10e8 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Tue, 24 Feb 2026 12:22:20 +1100 Subject: [PATCH 11/15] Fix attribute parser and kind names. For the attribute `FooBar` the parser is generally called `FooBarParser` and the kind is called `AttributeKind::FooBar`. This commit renames some cases that don't match that pattern. The most common cases: - Adding `Rustc` to the front of the parser name for a `rustc_*` attribute. - Adding `Parser` to the end of a parser name. - Slight word variations, e.g. `Deprecation` instead of `Deprecated`, `Pointer` instead of `Ptr`, `Stability` instead of `Stable`. --- .../src/attributes/allow_unstable.rs | 4 +- .../src/attributes/codegen_attrs.rs | 14 ++--- .../src/attributes/deprecation.rs | 7 ++- .../src/attributes/dummy.rs | 4 +- .../src/attributes/link_attrs.rs | 4 +- .../src/attributes/lint_helpers.rs | 16 +++--- .../rustc_attr_parsing/src/attributes/repr.rs | 18 +++---- .../src/attributes/rustc_internal.rs | 18 +++---- .../src/attributes/stability.rs | 6 +-- .../src/attributes/traits.rs | 32 ++++++------ .../src/attributes/transparency.rs | 4 +- compiler/rustc_attr_parsing/src/context.rs | 52 +++++++++---------- .../rustc_codegen_ssa/src/codegen_attrs.rs | 4 +- compiler/rustc_expand/src/base.rs | 2 +- .../rustc_hir/src/attrs/data_structures.rs | 26 +++++----- .../rustc_hir/src/attrs/encode_cross_crate.rs | 10 ++-- compiler/rustc_hir/src/hir.rs | 4 +- .../rustc_hir_analysis/src/check/check.rs | 2 +- compiler/rustc_lint/src/ptr_nulls.rs | 4 +- compiler/rustc_passes/src/check_attr.rs | 10 ++-- compiler/rustc_passes/src/stability.rs | 12 ++--- src/librustdoc/json/conversions.rs | 2 +- .../passes/collect_intra_doc_links.rs | 4 +- tests/pretty/delegation-inherit-attributes.pp | 6 +-- tests/pretty/delegation-inline-attribute.pp | 8 +-- tests/ui-fulldeps/internal-lints/find_attr.rs | 2 +- .../internal-lints/find_attr.stderr | 4 +- tests/ui/unpretty/deprecated-attr.stdout | 12 ++--- 28 files changed, 145 insertions(+), 146 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs index 4f1a8cd8b403c..c2511ac75d5d2 100644 --- a/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs +++ b/compiler/rustc_attr_parsing/src/attributes/allow_unstable.rs @@ -52,8 +52,8 @@ impl CombineAttributeParser for UnstableFeatureBoundParser { } } -pub(crate) struct AllowConstFnUnstableParser; -impl CombineAttributeParser for AllowConstFnUnstableParser { +pub(crate) struct RustcAllowConstFnUnstableParser; +impl CombineAttributeParser for RustcAllowConstFnUnstableParser { const PATH: &[Symbol] = &[sym::rustc_allow_const_fn_unstable]; type Item = Symbol; const CONVERT: ConvertFn = diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 485307622291e..4909e0d35173c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -153,9 +153,9 @@ impl SingleAttributeParser for ExportNameParser { } } -pub(crate) struct ObjcClassParser; +pub(crate) struct RustcObjcClassParser; -impl SingleAttributeParser for ObjcClassParser { +impl SingleAttributeParser for RustcObjcClassParser { const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_class]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; @@ -185,9 +185,9 @@ impl SingleAttributeParser for ObjcClassParser { } } -pub(crate) struct ObjcSelectorParser; +pub(crate) struct RustcObjcSelectorParser; -impl SingleAttributeParser for ObjcSelectorParser { +impl SingleAttributeParser for RustcObjcSelectorParser { const PATH: &[rustc_span::Symbol] = &[sym::rustc_objc_selector]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; @@ -709,13 +709,13 @@ impl NoArgsAttributeParser for RustcPassIndirectlyInNonRusticAbisPa const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassIndirectlyInNonRusticAbis; } -pub(crate) struct EiiForeignItemParser; +pub(crate) struct RustcEiiForeignItemParser; -impl NoArgsAttributeParser for EiiForeignItemParser { +impl NoArgsAttributeParser for RustcEiiForeignItemParser { const PATH: &[Symbol] = &[sym::rustc_eii_foreign_item]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]); - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::EiiForeignItem; + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcEiiForeignItem; } pub(crate) struct PatchableFunctionEntryParser; diff --git a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs index c055c2936e953..a2c7e459e0df8 100644 --- a/compiler/rustc_attr_parsing/src/attributes/deprecation.rs +++ b/compiler/rustc_attr_parsing/src/attributes/deprecation.rs @@ -7,8 +7,6 @@ use crate::session_diagnostics::{ DeprecatedItemSuggestion, InvalidSince, MissingNote, MissingSince, }; -pub(crate) struct DeprecationParser; - fn get( cx: &AcceptContext<'_, '_, S>, name: Symbol, @@ -33,7 +31,8 @@ fn get( } } -impl SingleAttributeParser for DeprecationParser { +pub(crate) struct DeprecatedParser; +impl SingleAttributeParser for DeprecatedParser { const PATH: &[Symbol] = &[sym::deprecated]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; @@ -164,7 +163,7 @@ impl SingleAttributeParser for DeprecationParser { return None; } - Some(AttributeKind::Deprecation { + Some(AttributeKind::Deprecated { deprecation: Deprecation { since, note, suggestion }, span: cx.attr_span, }) diff --git a/compiler/rustc_attr_parsing/src/attributes/dummy.rs b/compiler/rustc_attr_parsing/src/attributes/dummy.rs index 9f97af48afa05..71d10b23a37f6 100644 --- a/compiler/rustc_attr_parsing/src/attributes/dummy.rs +++ b/compiler/rustc_attr_parsing/src/attributes/dummy.rs @@ -7,8 +7,8 @@ use crate::context::{AcceptContext, Stage}; use crate::parser::ArgParser; use crate::target_checking::{ALL_TARGETS, AllowedTargets}; -pub(crate) struct DummyParser; -impl SingleAttributeParser for DummyParser { +pub(crate) struct RustcDummyParser; +impl SingleAttributeParser for RustcDummyParser { const PATH: &[Symbol] = &[sym::rustc_dummy]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 21c05611ce292..c4a483157a19d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -524,8 +524,8 @@ impl NoArgsAttributeParser for FfiPureParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure; } -pub(crate) struct StdInternalSymbolParser; -impl NoArgsAttributeParser for StdInternalSymbolParser { +pub(crate) struct RustcStdInternalSymbolParser; +impl NoArgsAttributeParser for RustcStdInternalSymbolParser { const PATH: &[Symbol] = &[sym::rustc_std_internal_symbol]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ diff --git a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs index 60e83a6083ede..76bddacd20bf4 100644 --- a/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs +++ b/compiler/rustc_attr_parsing/src/attributes/lint_helpers.rs @@ -1,7 +1,7 @@ use super::prelude::*; -pub(crate) struct AsPtrParser; -impl NoArgsAttributeParser for AsPtrParser { +pub(crate) struct RustcAsPtrParser; +impl NoArgsAttributeParser for RustcAsPtrParser { const PATH: &[Symbol] = &[sym::rustc_as_ptr]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -14,8 +14,8 @@ impl NoArgsAttributeParser for AsPtrParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcAsPtr; } -pub(crate) struct PubTransparentParser; -impl NoArgsAttributeParser for PubTransparentParser { +pub(crate) struct RustcPubTransparentParser; +impl NoArgsAttributeParser for RustcPubTransparentParser { const PATH: &[Symbol] = &[sym::rustc_pub_transparent]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -26,8 +26,8 @@ impl NoArgsAttributeParser for PubTransparentParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPubTransparent; } -pub(crate) struct PassByValueParser; -impl NoArgsAttributeParser for PassByValueParser { +pub(crate) struct RustcPassByValueParser; +impl NoArgsAttributeParser for RustcPassByValueParser { const PATH: &[Symbol] = &[sym::rustc_pass_by_value]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -38,8 +38,8 @@ impl NoArgsAttributeParser for PassByValueParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcPassByValue; } -pub(crate) struct RustcShouldNotBeCalledOnConstItems; -impl NoArgsAttributeParser for RustcShouldNotBeCalledOnConstItems { +pub(crate) struct RustcShouldNotBeCalledOnConstItemsParser; +impl NoArgsAttributeParser for RustcShouldNotBeCalledOnConstItemsParser { const PATH: &[Symbol] = &[sym::rustc_should_not_be_called_on_const_items]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ diff --git a/compiler/rustc_attr_parsing/src/attributes/repr.rs b/compiler/rustc_attr_parsing/src/attributes/repr.rs index fb0b8df652848..f0fc2a5b5e939 100644 --- a/compiler/rustc_attr_parsing/src/attributes/repr.rs +++ b/compiler/rustc_attr_parsing/src/attributes/repr.rs @@ -269,9 +269,9 @@ fn parse_alignment(node: &LitKind) -> Result { /// Parse #[align(N)]. #[derive(Default)] -pub(crate) struct AlignParser(Option<(Align, Span)>); +pub(crate) struct RustcAlignParser(Option<(Align, Span)>); -impl AlignParser { +impl RustcAlignParser { const PATH: &[Symbol] = &[sym::rustc_align]; const TEMPLATE: AttributeTemplate = template!(List: &[""]); @@ -308,7 +308,7 @@ impl AlignParser { } } -impl AttributeParser for AlignParser { +impl AttributeParser for RustcAlignParser { const ATTRIBUTES: AcceptMapping = &[(Self::PATH, Self::TEMPLATE, Self::parse)]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), @@ -321,29 +321,29 @@ impl AttributeParser for AlignParser { fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { let (align, span) = self.0?; - Some(AttributeKind::Align { align, span }) + Some(AttributeKind::RustcAlign { align, span }) } } #[derive(Default)] -pub(crate) struct AlignStaticParser(AlignParser); +pub(crate) struct RustcAlignStaticParser(RustcAlignParser); -impl AlignStaticParser { +impl RustcAlignStaticParser { const PATH: &[Symbol] = &[sym::rustc_align_static]; - const TEMPLATE: AttributeTemplate = AlignParser::TEMPLATE; + const TEMPLATE: AttributeTemplate = RustcAlignParser::TEMPLATE; fn parse(&mut self, cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) { self.0.parse(cx, args) } } -impl AttributeParser for AlignStaticParser { +impl AttributeParser for RustcAlignStaticParser { const ATTRIBUTES: AcceptMapping = &[(Self::PATH, Self::TEMPLATE, Self::parse)]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Static), Allow(Target::ForeignStatic)]); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { let (align, span) = self.0.0?; - Some(AttributeKind::Align { align, span }) + Some(AttributeKind::RustcAlign { align, span }) } } diff --git a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs index 2bd5b9a130d1c..5f63d8349a36a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs +++ b/compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs @@ -71,9 +71,9 @@ impl SingleAttributeParser for RustcMustImplementOneOfParser { } } -pub(crate) struct RustcNeverReturnsNullPointerParser; +pub(crate) struct RustcNeverReturnsNullPtrParser; -impl NoArgsAttributeParser for RustcNeverReturnsNullPointerParser { +impl NoArgsAttributeParser for RustcNeverReturnsNullPtrParser { const PATH: &[Symbol] = &[sym::rustc_never_returns_null_ptr]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ @@ -83,7 +83,7 @@ impl NoArgsAttributeParser for RustcNeverReturnsNullPointerParser { Allow(Target::Method(MethodKind::Trait { body: true })), Allow(Target::Method(MethodKind::TraitImpl)), ]); - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPointer; + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNeverReturnsNullPtr; } pub(crate) struct RustcNoImplicitAutorefsParser; @@ -1201,9 +1201,10 @@ impl NoArgsAttributeParser for RustcNonnullOptimizationGuaranteedPa const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNonnullOptimizationGuaranteed; } -pub(crate) struct RustcSymbolName; +pub(crate) struct RustcSymbolNameParser; -impl SingleAttributeParser for RustcSymbolName { +impl SingleAttributeParser for RustcSymbolNameParser { + const PATH: &[Symbol] = &[sym::rustc_symbol_name]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::TraitImpl)), @@ -1214,7 +1215,6 @@ impl SingleAttributeParser for RustcSymbolName { Allow(Target::Impl { of_trait: false }), ]); const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const PATH: &[Symbol] = &[sym::rustc_symbol_name]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { @@ -1226,9 +1226,10 @@ impl SingleAttributeParser for RustcSymbolName { } } -pub(crate) struct RustcDefPath; +pub(crate) struct RustcDefPathParser; -impl SingleAttributeParser for RustcDefPath { +impl SingleAttributeParser for RustcDefPathParser { + const PATH: &[Symbol] = &[sym::rustc_def_path]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::TraitImpl)), @@ -1239,7 +1240,6 @@ impl SingleAttributeParser for RustcDefPath { Allow(Target::Impl { of_trait: false }), ]); const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; - const PATH: &[Symbol] = &[sym::rustc_def_path]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const TEMPLATE: AttributeTemplate = template!(Word); fn convert(cx: &mut AcceptContext<'_, '_, S>, args: &ArgParser) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/stability.rs b/compiler/rustc_attr_parsing/src/attributes/stability.rs index a2be2d42b3e1a..941885d232c27 100644 --- a/compiler/rustc_attr_parsing/src/attributes/stability.rs +++ b/compiler/rustc_attr_parsing/src/attributes/stability.rs @@ -177,15 +177,15 @@ impl AttributeParser for BodyStabilityParser { } } -pub(crate) struct ConstStabilityIndirectParser; -impl NoArgsAttributeParser for ConstStabilityIndirectParser { +pub(crate) struct RustcConstStableIndirectParser; +impl NoArgsAttributeParser for RustcConstStableIndirectParser { const PATH: &[Symbol] = &[sym::rustc_const_stable_indirect]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Ignore; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[ Allow(Target::Fn), Allow(Target::Method(MethodKind::Inherent)), ]); - const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConstStabilityIndirect; + const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcConstStableIndirect; } #[derive(Default)] diff --git a/compiler/rustc_attr_parsing/src/attributes/traits.rs b/compiler/rustc_attr_parsing/src/attributes/traits.rs index ceaa43948d676..c09e151fc70c1 100644 --- a/compiler/rustc_attr_parsing/src/attributes/traits.rs +++ b/compiler/rustc_attr_parsing/src/attributes/traits.rs @@ -9,8 +9,8 @@ use crate::parser::ArgParser; use crate::target_checking::Policy::{Allow, Warn}; use crate::target_checking::{ALL_TARGETS, AllowedTargets}; -pub(crate) struct SkipDuringMethodDispatchParser; -impl SingleAttributeParser for SkipDuringMethodDispatchParser { +pub(crate) struct RustcSkipDuringMethodDispatchParser; +impl SingleAttributeParser for RustcSkipDuringMethodDispatchParser { const PATH: &[Symbol] = &[sym::rustc_skip_during_method_dispatch]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; @@ -58,8 +58,8 @@ impl SingleAttributeParser for SkipDuringMethodDispatchParser { } } -pub(crate) struct ParenSugarParser; -impl NoArgsAttributeParser for ParenSugarParser { +pub(crate) struct RustcParenSugarParser; +impl NoArgsAttributeParser for RustcParenSugarParser { const PATH: &[Symbol] = &[sym::rustc_paren_sugar]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); @@ -81,16 +81,16 @@ impl NoArgsAttributeParser for MarkerParser { const CREATE: fn(Span) -> AttributeKind = AttributeKind::Marker; } -pub(crate) struct DenyExplicitImplParser; -impl NoArgsAttributeParser for DenyExplicitImplParser { +pub(crate) struct RustcDenyExplicitImplParser; +impl NoArgsAttributeParser for RustcDenyExplicitImplParser { const PATH: &[Symbol] = &[sym::rustc_deny_explicit_impl]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcDenyExplicitImpl; } -pub(crate) struct DynIncompatibleTraitParser; -impl NoArgsAttributeParser for DynIncompatibleTraitParser { +pub(crate) struct RustcDynIncompatibleTraitParser; +impl NoArgsAttributeParser for RustcDynIncompatibleTraitParser { const PATH: &[Symbol] = &[sym::rustc_dyn_incompatible_trait]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); @@ -99,16 +99,16 @@ impl NoArgsAttributeParser for DynIncompatibleTraitParser { // Specialization -pub(crate) struct SpecializationTraitParser; -impl NoArgsAttributeParser for SpecializationTraitParser { +pub(crate) struct RustcSpecializationTraitParser; +impl NoArgsAttributeParser for RustcSpecializationTraitParser { const PATH: &[Symbol] = &[sym::rustc_specialization_trait]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcSpecializationTrait; } -pub(crate) struct UnsafeSpecializationMarkerParser; -impl NoArgsAttributeParser for UnsafeSpecializationMarkerParser { +pub(crate) struct RustcUnsafeSpecializationMarkerParser; +impl NoArgsAttributeParser for RustcUnsafeSpecializationMarkerParser { const PATH: &[Symbol] = &[sym::rustc_unsafe_specialization_marker]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); @@ -117,16 +117,16 @@ impl NoArgsAttributeParser for UnsafeSpecializationMarkerParser { // Coherence -pub(crate) struct CoinductiveParser; -impl NoArgsAttributeParser for CoinductiveParser { +pub(crate) struct RustcCoinductiveParser; +impl NoArgsAttributeParser for RustcCoinductiveParser { const PATH: &[Symbol] = &[sym::rustc_coinductive]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Trait)]); const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcCoinductive; } -pub(crate) struct AllowIncoherentImplParser; -impl NoArgsAttributeParser for AllowIncoherentImplParser { +pub(crate) struct RustcAllowIncoherentImplParser; +impl NoArgsAttributeParser for RustcAllowIncoherentImplParser { const PATH: &[Symbol] = &[sym::rustc_allow_incoherent_impl]; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Error; const ALLOWED_TARGETS: AllowedTargets = diff --git a/compiler/rustc_attr_parsing/src/attributes/transparency.rs b/compiler/rustc_attr_parsing/src/attributes/transparency.rs index 7fa4487b7c691..58b4a0b2fb1ad 100644 --- a/compiler/rustc_attr_parsing/src/attributes/transparency.rs +++ b/compiler/rustc_attr_parsing/src/attributes/transparency.rs @@ -2,9 +2,9 @@ use rustc_span::hygiene::Transparency; use super::prelude::*; -pub(crate) struct TransparencyParser; +pub(crate) struct RustcMacroTransparencyParser; -impl SingleAttributeParser for TransparencyParser { +impl SingleAttributeParser for RustcMacroTransparencyParser { const PATH: &[Symbol] = &[sym::rustc_macro_transparency]; const ATTRIBUTE_ORDER: AttributeOrder = AttributeOrder::KeepInnermost; const ON_DUPLICATE: OnDuplicate = OnDuplicate::Custom(|cx, used, unused| { diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b4e91ecebeeb7..a06c9c089c28c 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -139,27 +139,27 @@ macro_rules! attribute_parsers { attribute_parsers!( pub(crate) static ATTRIBUTE_PARSERS = [ // tidy-alphabetical-start - AlignParser, - AlignStaticParser, BodyStabilityParser, ConfusablesParser, ConstStabilityParser, DocParser, MacroUseParser, NakedParser, + RustcAlignParser, + RustcAlignStaticParser, RustcCguTestAttributeParser, StabilityParser, UsedParser, // tidy-alphabetical-end // tidy-alphabetical-start - Combine, Combine, Combine, Combine, Combine, Combine, Combine, + Combine, Combine, Combine, Combine, @@ -174,9 +174,8 @@ attribute_parsers!( Single, Single, Single, - Single, + Single, Single, - Single, Single, Single, Single, @@ -190,8 +189,6 @@ attribute_parsers!( Single, Single, Single, - Single, - Single, Single, Single, Single, @@ -202,44 +199,40 @@ attribute_parsers!( Single, Single, Single, - Single, + Single, Single, Single, Single, + Single, Single, Single, Single, Single, Single, Single, + Single, Single, Single, + Single, + Single, Single, Single, Single, - Single, + Single, + Single, Single, Single, Single, - Single, Single, - Single, Single, Single, - Single>, Single>, - Single>, Single>, - Single>, Single>, Single>, Single>, - Single>, Single>, Single>, - Single>, - Single>, - Single>, Single>, Single>, Single>, @@ -260,29 +253,33 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, - Single>, Single>, Single>, Single>, Single>, Single>, Single>, - Single>, Single>, Single>, + Single>, + Single>, Single>, Single>, + Single>, + Single>, Single>, Single>, Single>, + Single>, Single>, Single>, Single>, Single>, Single>, Single>, + Single>, Single>, + Single>, Single>, Single>, Single>, @@ -293,7 +290,7 @@ attribute_parsers!( Single>, Single>, Single>, - Single>, + Single>, Single>, Single>, Single>, @@ -303,21 +300,24 @@ attribute_parsers!( Single>, Single>, Single>, + Single>, + Single>, Single>, Single>, Single>, + Single>, Single>, Single>, - Single>, + Single>, + Single>, + Single>, Single>, Single>, + Single>, Single>, Single>, - Single>, - Single>, Single>, Single>, - Single>, // tidy-alphabetical-end ]; ); diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs index 6b0d46f5dc5d1..43f039cc5ebfd 100644 --- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs +++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs @@ -80,7 +80,7 @@ fn process_builtin_attrs( interesting_spans.inline = Some(*span); } AttributeKind::Naked(_) => codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED, - AttributeKind::Align { align, .. } => codegen_fn_attrs.alignment = Some(*align), + AttributeKind::RustcAlign { align, .. } => codegen_fn_attrs.alignment = Some(*align), AttributeKind::LinkName { name, .. } => { // FIXME Remove check for foreign functions once #[link_name] on non-foreign // functions is a hard error @@ -223,7 +223,7 @@ fn process_builtin_attrs( AttributeKind::RustcObjcSelector { methname, .. } => { codegen_fn_attrs.objc_selector = Some(*methname); } - AttributeKind::EiiForeignItem => { + AttributeKind::RustcEiiForeignItem => { codegen_fn_attrs.flags |= CodegenFnAttrFlags::EXTERNALLY_IMPLEMENTABLE_ITEM; } AttributeKind::EiiImpls(impls) => { diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index ae3022b83e564..225906dfba2de 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -974,7 +974,7 @@ impl SyntaxExtension { stability, deprecation: find_attr!( attrs, - Deprecation { deprecation, .. } => *deprecation + Deprecated { deprecation, .. } => *deprecation ), helper_attrs, edition, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 5d154cef66a65..54582ee653446 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -845,13 +845,6 @@ pub struct RustcCleanQueries { #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum AttributeKind { // tidy-alphabetical-start - /// Represents `#[align(N)]`. - // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity - Align { - align: Align, - span: Span, - }, - /// Represents `#[allow_internal_unsafe]`. AllowInternalUnsafe(Span), @@ -910,7 +903,7 @@ pub enum AttributeKind { DefaultLibAllocator, /// Represents [`#[deprecated]`](https://doc.rust-lang.org/stable/reference/attributes/diagnostics.html#the-deprecated-attribute). - Deprecation { + Deprecated { deprecation: Deprecation, span: Span, }, @@ -937,9 +930,6 @@ pub enum AttributeKind { /// Implementation detail of `#[eii]` EiiDeclaration(EiiDecl), - /// Implementation detail of `#[eii]` - EiiForeignItem, - /// Implementation detail of `#[eii]` EiiImpls(ThinVec), @@ -1148,6 +1138,13 @@ pub enum AttributeKind { kind: RustcAbiAttrKind, }, + /// Represents `#[align(N)]`. + // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity + RustcAlign { + align: Align, + span: Span, + }, + /// Represents `#[rustc_allocator]` RustcAllocator, @@ -1209,7 +1206,7 @@ pub enum AttributeKind { }, /// Represents `#[rustc_const_stable_indirect]`. - RustcConstStabilityIndirect, + RustcConstStableIndirect, /// Represents `#[rustc_conversion_suggestion]` RustcConversionSuggestion, @@ -1263,6 +1260,9 @@ pub enum AttributeKind { /// Represents `#[rustc_effective_visibility]`. RustcEffectiveVisibility, + /// Implementation detail of `#[eii]` + RustcEiiForeignItem, + /// Represents `#[rustc_evaluate_where_clauses]` RustcEvaluateWhereClauses, @@ -1328,7 +1328,7 @@ pub enum AttributeKind { }, /// Represents `#[rustc_never_returns_null_ptr]` - RustcNeverReturnsNullPointer, + RustcNeverReturnsNullPtr, /// Represents `#[rustc_never_type_options]`. RustcNeverTypeOptions { diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 0b20ea4d6a83a..a121b9cc0dc6b 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -18,7 +18,6 @@ impl AttributeKind { match self { // tidy-alphabetical-start - Align { .. } => No, AllowInternalUnsafe(..) => Yes, AllowInternalUnstable(..) => Yes, AutomaticallyDerived(..) => Yes, @@ -36,12 +35,11 @@ impl AttributeKind { CustomMir(_, _, _) => Yes, DebuggerVisualizer(..) => No, DefaultLibAllocator => No, - Deprecation { .. } => Yes, + Deprecated { .. } => Yes, DoNotRecommend { .. } => Yes, Doc(_) => Yes, DocComment { .. } => Yes, EiiDeclaration(_) => Yes, - EiiForeignItem => No, EiiImpls(..) => No, ExportName { .. } => Yes, ExportStable => No, @@ -93,6 +91,7 @@ impl AttributeKind { ReexportTestHarnessMain(..) => No, Repr { .. } => No, RustcAbi { .. } => No, + RustcAlign { .. } => No, RustcAllocator => No, RustcAllocatorZeroed => No, RustcAllocatorZeroedVariant { .. } => Yes, @@ -108,7 +107,7 @@ impl AttributeKind { RustcCoinductive(..) => No, RustcConfusables { .. } => Yes, RustcConstStability { .. } => Yes, - RustcConstStabilityIndirect => No, + RustcConstStableIndirect => No, RustcConversionSuggestion => Yes, RustcDeallocator => No, RustcDefPath(..) => No, @@ -126,6 +125,7 @@ impl AttributeKind { RustcDumpVtable(..) => No, RustcDynIncompatibleTrait(..) => No, RustcEffectiveVisibility => Yes, + RustcEiiForeignItem => No, RustcEvaluateWhereClauses => Yes, RustcHasIncoherentInherentImpls => Yes, RustcHiddenTypeOfOpaques => No, @@ -145,7 +145,7 @@ impl AttributeKind { RustcMain => No, RustcMir(..) => Yes, RustcMustImplementOneOf { .. } => No, - RustcNeverReturnsNullPointer => Yes, + RustcNeverReturnsNullPtr => Yes, RustcNeverTypeOptions { .. } => No, RustcNoImplicitAutorefs => Yes, RustcNoImplicitBounds => No, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 960fd206041a8..373d61978439e 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1378,7 +1378,7 @@ impl AttributeExt for Attribute { Attribute::Unparsed(u) => u.span, // FIXME: should not be needed anymore when all attrs are parsed Attribute::Parsed(AttributeKind::DocComment { span, .. }) => *span, - Attribute::Parsed(AttributeKind::Deprecation { span, .. }) => *span, + Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span, Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) => cfgs[0].1, a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"), } @@ -1420,7 +1420,7 @@ impl AttributeExt for Attribute { #[inline] fn deprecation_note(&self) -> Option { match &self { - Attribute::Parsed(AttributeKind::Deprecation { deprecation, .. }) => deprecation.note, + Attribute::Parsed(AttributeKind::Deprecated { deprecation, .. }) => deprecation.note, _ => None, } } diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 349ad4f7fc43b..8439ab1349d17 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -980,7 +980,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), (0, _) => ("const", "consts", None), _ => ("type or const", "types or consts", None), }; - let name = if find_attr!(tcx, def_id, EiiForeignItem) { + let name = if find_attr!(tcx, def_id, RustcEiiForeignItem) { "externally implementable items" } else { "foreign items" diff --git a/compiler/rustc_lint/src/ptr_nulls.rs b/compiler/rustc_lint/src/ptr_nulls.rs index 18a80684fa44e..1e1bbf51fcb9b 100644 --- a/compiler/rustc_lint/src/ptr_nulls.rs +++ b/compiler/rustc_lint/src/ptr_nulls.rs @@ -72,14 +72,14 @@ fn useless_check<'a, 'tcx: 'a>( e = e.peel_blocks(); if let ExprKind::MethodCall(_, _expr, [], _) = e.kind && let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) - && find_attr!(cx.tcx, def_id, RustcNeverReturnsNullPointer) + && find_attr!(cx.tcx, def_id, RustcNeverReturnsNullPtr) && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) { return Some(UselessPtrNullChecksDiag::FnRet { fn_name }); } else if let ExprKind::Call(path, _args) = e.kind && let ExprKind::Path(ref qpath) = path.kind && let Some(def_id) = cx.qpath_res(qpath, path.hir_id).opt_def_id() - && find_attr!(cx.tcx, def_id, RustcNeverReturnsNullPointer) + && find_attr!(cx.tcx, def_id, RustcNeverReturnsNullPtr) && let Some(fn_name) = cx.tcx.opt_item_ident(def_id) { return Some(UselessPtrNullChecksDiag::FnRet { fn_name }); diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 9af9398d78b96..cd2ad8ec43963 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -178,7 +178,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::RustcAllowConstFnUnstable(_, first_span)) => { self.check_rustc_allow_const_fn_unstable(hir_id, *first_span, span, target) } - Attribute::Parsed(AttributeKind::Deprecation {span: attr_span, .. }) => { + Attribute::Parsed(AttributeKind::Deprecated { span: attr_span, .. }) => { self.check_deprecated(hir_id, *attr_span, target) } Attribute::Parsed(AttributeKind::TargetFeature{ attr_span, ..}) => { @@ -190,7 +190,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { &Attribute::Parsed(AttributeKind::RustcPubTransparent(attr_span)) => { self.check_rustc_pub_transparent(attr_span, span, attrs) } - Attribute::Parsed(AttributeKind::Align { align, span: attr_span }) => { + Attribute::Parsed(AttributeKind::RustcAlign { align, span: attr_span }) => { self.check_align(*align, *attr_span) } Attribute::Parsed(AttributeKind::Naked(..)) => { @@ -250,7 +250,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { // `#[doc]` is actually a lot more than just doc comments, so is checked below | AttributeKind::DocComment {..} | AttributeKind::EiiDeclaration { .. } - | AttributeKind::EiiForeignItem | AttributeKind::ExportName { .. } | AttributeKind::ExportStable | AttributeKind::FfiConst(..) @@ -303,7 +302,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcCoherenceIsCore(..) | AttributeKind::RustcCoinductive(..) | AttributeKind::RustcConfusables { .. } - | AttributeKind::RustcConstStabilityIndirect + | AttributeKind::RustcConstStableIndirect | AttributeKind::RustcConversionSuggestion | AttributeKind::RustcDeallocator | AttributeKind::RustcDefPath(..) @@ -321,6 +320,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcDumpVtable(..) | AttributeKind::RustcDynIncompatibleTrait(..) | AttributeKind::RustcEffectiveVisibility + | AttributeKind::RustcEiiForeignItem | AttributeKind::RustcEvaluateWhereClauses | AttributeKind::RustcHasIncoherentInherentImpls | AttributeKind::RustcHiddenTypeOfOpaques @@ -338,7 +338,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::RustcMacroTransparency(_) | AttributeKind::RustcMain | AttributeKind::RustcMir(_) - | AttributeKind::RustcNeverReturnsNullPointer + | AttributeKind::RustcNeverReturnsNullPtr | AttributeKind::RustcNeverTypeOptions {..} | AttributeKind::RustcNoImplicitAutorefs | AttributeKind::RustcNoImplicitBounds diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 6fd28c78740cd..97d09e39b08b8 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -97,7 +97,7 @@ fn annotation_kind(tcx: TyCtxt<'_>, def_id: LocalDefId) -> AnnotationKind { fn lookup_deprecation_entry(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option { let depr = find_attr!(tcx, def_id, - Deprecation { deprecation, span: _ } => *deprecation + Deprecated { deprecation, span: _ } => *deprecation ); let Some(depr) = depr else { @@ -209,22 +209,22 @@ fn lookup_const_stability(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option *stability); // After checking the immediate attributes, get rid of the span and compute implied // const stability: inherit feature gate from regular stability. let mut const_stab = const_stab - .map(|const_stab| ConstStability::from_partial(const_stab, const_stability_indirect)); + .map(|const_stab| ConstStability::from_partial(const_stab, const_stable_indirect)); // If this is a const fn but not annotated with stability markers, see if we can inherit // regular stability. @@ -335,7 +335,7 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> { if stab.is_none() && depr.map_or(false, |d| d.attr.is_since_rustc_version()) - && let Some(span) = find_attr_span!(Deprecation) + && let Some(span) = find_attr_span!(Deprecated) { self.tcx.dcx().emit_err(errors::DeprecatedAttribute { span }); } diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 599d7b10005d9..c42ea8bf1e2d1 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -916,7 +916,7 @@ fn maybe_from_hir_attr(attr: &hir::Attribute, item_id: ItemId, tcx: TyCtxt<'_>) }; vec![match kind { - AK::Deprecation { .. } => return Vec::new(), // Handled separately into Item::deprecation. + AK::Deprecated { .. } => return Vec::new(), // Handled separately into Item::deprecation. AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"), AK::MacroExport { .. } => Attribute::MacroExport, diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index 1da191c903871..47a92be3ec6ef 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -1109,7 +1109,7 @@ impl LinkCollector<'_, '_> { // Also resolve links in the note text of `#[deprecated]`. for attr in &item.attrs.other_attrs { - let Attribute::Parsed(AttributeKind::Deprecation { span: depr_span, deprecation }) = + let Attribute::Parsed(AttributeKind::Deprecated { span: depr_span, deprecation }) = attr else { continue; @@ -1127,7 +1127,7 @@ impl LinkCollector<'_, '_> { // inlined item. // let item_id = if let Some(inline_stmt_id) = item.inline_stmt_id - && find_attr!(tcx, inline_stmt_id, Deprecation {span, ..} if span == depr_span) + && find_attr!(tcx, inline_stmt_id, Deprecated { span, ..} if span == depr_span) { inline_stmt_id.to_def_id() } else { diff --git a/tests/pretty/delegation-inherit-attributes.pp b/tests/pretty/delegation-inherit-attributes.pp index 26ca5e99b885d..3182bc2838454 100644 --- a/tests/pretty/delegation-inherit-attributes.pp +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -22,11 +22,11 @@ fn foo_no_reason(x: usize) -> usize { x } #[attr = Cold] - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] fn bar(x: usize) -> usize { x } } -#[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] #[attr = MustUse {reason: "foo: some reason"}] #[attr = Inline(Hint)] fn foo1(arg0: _) -> _ { to_reuse::foo(self + 1) } @@ -35,7 +35,7 @@ #[attr = Inline(Hint)] fn foo_no_reason(arg0: _) -> _ { to_reuse::foo_no_reason(self + 1) } -#[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] #[attr = MustUse {reason: "some reason"}] #[attr = Inline(Hint)] fn foo2(arg0: _) -> _ { to_reuse::foo(self + 1) } diff --git a/tests/pretty/delegation-inline-attribute.pp b/tests/pretty/delegation-inline-attribute.pp index 9f362fa863f82..2d2caf28da55f 100644 --- a/tests/pretty/delegation-inline-attribute.pp +++ b/tests/pretty/delegation-inline-attribute.pp @@ -46,7 +46,7 @@ // Check that #[inline(hint)] is added when other attributes present in inner reuse #[attr = Cold] #[attr = MustUse] - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] #[attr = Inline(Hint)] fn foo1(arg0: _) -> _ { to_reuse::foo(self / 2) } @@ -62,7 +62,7 @@ #[attr = Cold] #[attr = MustUse] #[attr = Inline(Never)] - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] fn foo4(arg0: _) -> _ { to_reuse::foo(self / 2) } }.foo() } @@ -70,7 +70,7 @@ // Check that #[inline(hint)] is added when there are other attributes present in trait reuse #[attr = Cold] #[attr = MustUse] - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] #[attr = Inline(Hint)] fn foo1(self: _) -> _ { self.0.foo1() } @@ -86,7 +86,7 @@ #[attr = Cold] #[attr = MustUse] #[attr = Inline(Never)] - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] fn foo4(self: _) -> _ { self.0.foo4() } } diff --git a/tests/ui-fulldeps/internal-lints/find_attr.rs b/tests/ui-fulldeps/internal-lints/find_attr.rs index bb0c8c486bcdb..90b9b96ba54ef 100644 --- a/tests/ui-fulldeps/internal-lints/find_attr.rs +++ b/tests/ui-fulldeps/internal-lints/find_attr.rs @@ -13,7 +13,7 @@ fn main() { find_attr!(attrs, AttributeKind::Inline(..)); //~^ ERROR use of `AttributeKind` in `find_attr!(...)` invocation - find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecation {..}); + find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecated {..}); //~^ ERROR use of `AttributeKind` in `find_attr!(...)` invocation //~| ERROR use of `AttributeKind` in `find_attr!(...)` invocation diff --git a/tests/ui-fulldeps/internal-lints/find_attr.stderr b/tests/ui-fulldeps/internal-lints/find_attr.stderr index b7291ecbb84d9..8cb1003b0aa1e 100644 --- a/tests/ui-fulldeps/internal-lints/find_attr.stderr +++ b/tests/ui-fulldeps/internal-lints/find_attr.stderr @@ -15,7 +15,7 @@ LL | #![deny(rustc::bad_use_of_find_attr)] error: use of `AttributeKind` in `find_attr!(...)` invocation --> $DIR/find_attr.rs:16:23 | -LL | find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecation {..}); +LL | find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecated {..}); | ^^^^^^^^^^^^^ | = note: `find_attr!(...)` already imports `AttributeKind::*` @@ -24,7 +24,7 @@ LL | find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecatio error: use of `AttributeKind` in `find_attr!(...)` invocation --> $DIR/find_attr.rs:16:51 | -LL | find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecation {..}); +LL | find_attr!(attrs, AttributeKind::Inline{..} | AttributeKind::Deprecated {..}); | ^^^^^^^^^^^^^ | = note: `find_attr!(...)` already imports `AttributeKind::*` diff --git a/tests/ui/unpretty/deprecated-attr.stdout b/tests/ui/unpretty/deprecated-attr.stdout index 32d5cf06a3d67..4ba5b0b366df9 100644 --- a/tests/ui/unpretty/deprecated-attr.stdout +++ b/tests/ui/unpretty/deprecated-attr.stdout @@ -5,28 +5,28 @@ use ::std::prelude::rust_2015::*; //@ check-pass //@ edition: 2015 -#[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] struct PlainDeprecated; -#[attr = Deprecation {deprecation: Deprecation {since: Unspecified, +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified, note: here's why this is deprecated#0}}] struct DirectNote; -#[attr = Deprecation {deprecation: Deprecation {since: Unspecified, +#[attr = Deprecated {deprecation: Deprecation {since: Unspecified, note: here's why this is deprecated#0}}] struct ExplicitNote; -#[attr = Deprecation {deprecation: Deprecation {since: NonStandard("1.2.3"), +#[attr = Deprecated {deprecation: Deprecation {since: NonStandard("1.2.3"), note: here's why this is deprecated#0}}] struct SinceAndNote; -#[attr = Deprecation {deprecation: Deprecation {since: NonStandard("1.2.3"), +#[attr = Deprecated {deprecation: Deprecation {since: NonStandard("1.2.3"), note: here's why this is deprecated#0}}] struct FlippedOrder; fn f() { // Attribute is ignored here (with a warning), but still preserved in HIR - #[attr = Deprecation {deprecation: Deprecation {since: Unspecified}}] + #[attr = Deprecated {deprecation: Deprecation {since: Unspecified}}] 0 } From bf0f5115c3530e41fa73e5ef47d4bd7d37e1d23a Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 24 Feb 2026 13:00:15 +1100 Subject: [PATCH 12/15] Clarify how "ensure" queries check whether they can skip execution --- compiler/rustc_middle/src/query/inner.rs | 15 ++-- compiler/rustc_middle/src/query/mod.rs | 4 +- compiler/rustc_middle/src/query/plumbing.rs | 18 ++++- compiler/rustc_query_impl/src/execution.rs | 79 ++++++++++++++------- 4 files changed, 79 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_middle/src/query/inner.rs b/compiler/rustc_middle/src/query/inner.rs index 0b575b536cb6e..4e62fbbec77d5 100644 --- a/compiler/rustc_middle/src/query/inner.rs +++ b/compiler/rustc_middle/src/query/inner.rs @@ -1,13 +1,14 @@ //! Helper functions that serve as the immediate implementation of //! `tcx.$query(..)` and its variations. +use rustc_data_structures::assert_matches; use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; use crate::dep_graph; use crate::dep_graph::{DepKind, DepNodeKey}; use crate::query::erase::{self, Erasable, Erased}; use crate::query::plumbing::QueryVTable; -use crate::query::{QueryCache, QueryMode}; +use crate::query::{EnsureMode, QueryCache, QueryMode}; use crate::ty::TyCtxt; /// Checks whether there is already a value for this key in the in-memory @@ -56,12 +57,12 @@ pub(crate) fn query_ensure<'tcx, Cache>( execute_query: fn(TyCtxt<'tcx>, Span, Cache::Key, QueryMode) -> Option, query_cache: &Cache, key: Cache::Key, - check_cache: bool, + ensure_mode: EnsureMode, ) where Cache: QueryCache, { if try_get_cached(tcx, query_cache, &key).is_none() { - execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { check_cache }); + execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { ensure_mode }); } } @@ -73,16 +74,20 @@ pub(crate) fn query_ensure_error_guaranteed<'tcx, Cache, T>( execute_query: fn(TyCtxt<'tcx>, Span, Cache::Key, QueryMode) -> Option, query_cache: &Cache, key: Cache::Key, - check_cache: bool, + // This arg is needed to match the signature of `query_ensure`, + // but should always be `EnsureMode::Ok`. + ensure_mode: EnsureMode, ) -> Result<(), ErrorGuaranteed> where Cache: QueryCache>>, Result: Erasable, { + assert_matches!(ensure_mode, EnsureMode::Ok); + if let Some(res) = try_get_cached(tcx, query_cache, &key) { erase::restore_val(res).map(drop) } else { - execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { check_cache }) + execute_query(tcx, DUMMY_SP, key, QueryMode::Ensure { ensure_mode }) .map(erase::restore_val) .map(|res| res.map(drop)) // Either we actually executed the query, which means we got a full `Result`, diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 66e4a77ea6a51..bb457ab03fb55 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -6,8 +6,8 @@ pub use self::caches::{ pub use self::job::{QueryInfo, QueryJob, QueryJobId, QueryLatch, QueryWaiter}; pub use self::keys::{AsLocalKey, Key, LocalCrate}; pub use self::plumbing::{ - ActiveKeyStatus, CycleError, CycleErrorHandling, IntoQueryParam, QueryMode, QueryState, - TyCtxtAt, TyCtxtEnsureDone, TyCtxtEnsureOk, + ActiveKeyStatus, CycleError, CycleErrorHandling, EnsureMode, IntoQueryParam, QueryMode, + QueryState, TyCtxtAt, TyCtxtEnsureDone, TyCtxtEnsureOk, }; pub use self::stack::{QueryStackDeferred, QueryStackFrame, QueryStackFrameExtra}; pub use crate::queries::Providers; diff --git a/compiler/rustc_middle/src/query/plumbing.rs b/compiler/rustc_middle/src/query/plumbing.rs index 9652be2551629..9be30bdfcfa8e 100644 --- a/compiler/rustc_middle/src/query/plumbing.rs +++ b/compiler/rustc_middle/src/query/plumbing.rs @@ -97,8 +97,20 @@ impl<'tcx> CycleError> { #[derive(Debug)] pub enum QueryMode { + /// This is a normal query call to `tcx.$query(..)` or `tcx.at(span).$query(..)`. Get, - Ensure { check_cache: bool }, + /// This is a call to `tcx.ensure_ok().$query(..)` or `tcx.ensure_done().$query(..)`. + Ensure { ensure_mode: EnsureMode }, +} + +/// Distinguishes between `tcx.ensure_ok()` and `tcx.ensure_done()` in shared +/// code paths that handle both modes. +#[derive(Debug)] +pub enum EnsureMode { + /// Corresponds to [`TyCtxt::ensure_ok`]. + Ok, + /// Corresponds to [`TyCtxt::ensure_done`]. + Done, } /// Stores function pointers and other metadata for a particular query. @@ -526,7 +538,7 @@ macro_rules! define_callbacks { self.tcx.query_system.fns.engine.$name, &self.tcx.query_system.caches.$name, $crate::query::IntoQueryParam::into_query_param(key), - false, + $crate::query::EnsureMode::Ok, ) } )* @@ -542,7 +554,7 @@ macro_rules! define_callbacks { self.tcx.query_system.fns.engine.$name, &self.tcx.query_system.caches.$name, $crate::query::IntoQueryParam::into_query_param(key), - true, + $crate::query::EnsureMode::Done, ); } )* diff --git a/compiler/rustc_query_impl/src/execution.rs b/compiler/rustc_query_impl/src/execution.rs index 53afcacb63a6c..7cc20fef6c3db 100644 --- a/compiler/rustc_query_impl/src/execution.rs +++ b/compiler/rustc_query_impl/src/execution.rs @@ -8,8 +8,8 @@ use rustc_errors::{Diag, FatalError, StashKey}; use rustc_middle::dep_graph::{DepGraphData, DepNodeKey}; use rustc_middle::query::plumbing::QueryVTable; use rustc_middle::query::{ - ActiveKeyStatus, CycleError, CycleErrorHandling, QueryCache, QueryJob, QueryJobId, QueryLatch, - QueryMode, QueryStackDeferred, QueryStackFrame, QueryState, + ActiveKeyStatus, CycleError, CycleErrorHandling, EnsureMode, QueryCache, QueryJob, QueryJobId, + QueryLatch, QueryMode, QueryStackDeferred, QueryStackFrame, QueryState, }; use rustc_middle::ty::TyCtxt; use rustc_middle::verify_ich::incremental_verify_ich; @@ -276,6 +276,8 @@ fn try_execute_query<'tcx, C: QueryCache, const INCR: bool>( tcx: TyCtxt<'tcx>, span: Span, key: C::Key, + // If present, some previous step has already created a `DepNode` for this + // query+key, which we should reuse instead of creating a new one. dep_node: Option, ) -> (C::Value, Option) { let state = query.query_state(tcx); @@ -582,23 +584,32 @@ fn try_load_from_disk_and_cache_in_memory<'tcx, C: QueryCache>( Some((result, dep_node_index)) } -/// Ensure that either this query has all green inputs or been executed. -/// Executing `query::ensure(D)` is considered a read of the dep-node `D`. -/// Returns true if the query should still run. -/// -/// This function is particularly useful when executing passes for their -/// side-effects -- e.g., in order to report errors for erroneous programs. +/// Return value struct for [`check_if_ensure_can_skip_execution`]. +struct EnsureCanSkip { + /// If true, the current `tcx.ensure_ok()` or `tcx.ensure_done()` query + /// can return early without actually trying to execute. + skip_execution: bool, + /// A dep node that was prepared while checking whether execution can be + /// skipped, to be reused by execution itself if _not_ skipped. + dep_node: Option, +} + +/// Checks whether a `tcx.ensure_ok()` or `tcx.ensure_done()` query call can +/// return early without actually trying to execute. /// -/// Note: The optimization is only available during incr. comp. +/// This only makes sense during incremental compilation, because it relies +/// on having the dependency graph (and in some cases a disk-cached value) +/// from the previous incr-comp session. #[inline(never)] -fn ensure_must_run<'tcx, C: QueryCache>( +fn check_if_ensure_can_skip_execution<'tcx, C: QueryCache>( query: &'tcx QueryVTable<'tcx, C>, tcx: TyCtxt<'tcx>, key: &C::Key, - check_cache: bool, -) -> (bool, Option) { + ensure_mode: EnsureMode, +) -> EnsureCanSkip { + // Queries with `eval_always` should never skip execution. if query.eval_always { - return (true, None); + return EnsureCanSkip { skip_execution: false, dep_node: None }; } // Ensuring an anonymous query makes no sense @@ -615,7 +626,7 @@ fn ensure_must_run<'tcx, C: QueryCache>( // DepNodeIndex. We must invoke the query itself. The performance cost // this introduces should be negligible as we'll immediately hit the // in-memory cache, or another query down the line will. - return (true, Some(dep_node)); + return EnsureCanSkip { skip_execution: false, dep_node: Some(dep_node) }; } Some((serialized_dep_node_index, dep_node_index)) => { dep_graph.read_index(dep_node_index); @@ -624,13 +635,21 @@ fn ensure_must_run<'tcx, C: QueryCache>( } }; - // We do not need the value at all, so do not check the cache. - if !check_cache { - return (false, None); + match ensure_mode { + EnsureMode::Ok => { + // In ensure-ok mode, we can skip execution for this key if the node + // is green. It must have succeeded in the previous session, and + // therefore would succeed in the current session if executed. + EnsureCanSkip { skip_execution: true, dep_node: None } + } + EnsureMode::Done => { + // In ensure-done mode, we can only skip execution for this key if + // there's a disk-cached value available to load later if needed, + // which guarantees the query provider will never run for this key. + let is_loadable = query.is_loadable_from_disk(tcx, key, serialized_dep_node_index); + EnsureCanSkip { skip_execution: is_loadable, dep_node: Some(dep_node) } + } } - - let loadable = query.is_loadable_from_disk(tcx, key, serialized_dep_node_index); - (!loadable, Some(dep_node)) } #[inline(always)] @@ -655,14 +674,20 @@ pub(super) fn get_query_incr<'tcx, C: QueryCache>( ) -> Option { debug_assert!(tcx.dep_graph.is_fully_enabled()); - let dep_node = if let QueryMode::Ensure { check_cache } = mode { - let (must_run, dep_node) = ensure_must_run(query, tcx, &key, check_cache); - if !must_run { - return None; + // Check if query execution can be skipped, for `ensure_ok` or `ensure_done`. + // This might have the side-effect of creating a suitable DepNode, which + // we should reuse for execution instead of creating a new one. + let dep_node: Option = match mode { + QueryMode::Ensure { ensure_mode } => { + let EnsureCanSkip { skip_execution, dep_node } = + check_if_ensure_can_skip_execution(query, tcx, &key, ensure_mode); + if skip_execution { + // Return early to skip execution. + return None; + } + dep_node } - dep_node - } else { - None + QueryMode::Get => None, }; let (result, dep_node_index) = From 16fbd29fcfc592734d2968c272b188d095ced1d3 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 23 Feb 2026 14:25:56 +1100 Subject: [PATCH 13/15] Streamline `QueryVTableUnerased` into `GetQueryVTable` --- .../rustc_query_impl/src/dep_kind_vtables.rs | 9 ++-- compiler/rustc_query_impl/src/lib.rs | 14 ++---- compiler/rustc_query_impl/src/plumbing.rs | 49 ++++++------------- 3 files changed, 25 insertions(+), 47 deletions(-) diff --git a/compiler/rustc_query_impl/src/dep_kind_vtables.rs b/compiler/rustc_query_impl/src/dep_kind_vtables.rs index 882e59cfa1f3a..f892ff05214f9 100644 --- a/compiler/rustc_query_impl/src/dep_kind_vtables.rs +++ b/compiler/rustc_query_impl/src/dep_kind_vtables.rs @@ -2,7 +2,7 @@ use rustc_middle::bug; use rustc_middle::dep_graph::{DepKindVTable, DepNodeKey, KeyFingerprintStyle}; use rustc_middle::query::QueryCache; -use crate::QueryDispatcherUnerased; +use crate::GetQueryVTable; use crate::plumbing::{force_from_dep_node_inner, try_load_from_on_disk_cache_inner}; /// [`DepKindVTable`] constructors for special dep kinds that aren't queries. @@ -102,18 +102,17 @@ mod non_query { /// Shared implementation of the [`DepKindVTable`] constructor for queries. /// Called from macro-generated code for each query. -pub(crate) fn make_dep_kind_vtable_for_query<'tcx, Q, Cache>( +pub(crate) fn make_dep_kind_vtable_for_query<'tcx, Q>( is_anon: bool, is_eval_always: bool, ) -> DepKindVTable<'tcx> where - Q: QueryDispatcherUnerased<'tcx, Cache>, - Cache: QueryCache + 'tcx, + Q: GetQueryVTable<'tcx>, { let key_fingerprint_style = if is_anon { KeyFingerprintStyle::Opaque } else { - >::key_fingerprint_style() + ::Key::key_fingerprint_style() }; if is_anon || !key_fingerprint_style.reconstructible() { diff --git a/compiler/rustc_query_impl/src/lib.rs b/compiler/rustc_query_impl/src/lib.rs index 41a947bb4a84c..63cba4bb6172b 100644 --- a/compiler/rustc_query_impl/src/lib.rs +++ b/compiler/rustc_query_impl/src/lib.rs @@ -37,22 +37,18 @@ mod job; mod profiling_support; mod values; -/// Provides access to vtable-like operations for a query (by obtaining a -/// `QueryVTable`), but also keeps track of the "unerased" value type of the -/// query (i.e. the actual result type in the query declaration). +/// Trait that knows how to look up the [`QueryVTable`] for a particular query. /// /// This trait allows some per-query code to be defined in generic functions /// with a trait bound, instead of having to be defined inline within a macro /// expansion. /// /// There is one macro-generated implementation of this trait for each query, -/// on the type `rustc_query_impl::query_impl::$name::QueryType`. -trait QueryDispatcherUnerased<'tcx, C: QueryCache> { - type UnerasedValue; +/// on the type `rustc_query_impl::query_impl::$name::VTableGetter`. +trait GetQueryVTable<'tcx> { + type Cache: QueryCache + 'tcx; - fn query_vtable(tcx: TyCtxt<'tcx>) -> &'tcx QueryVTable<'tcx, C>; - - fn restore_val(value: C::Value) -> Self::UnerasedValue; + fn query_vtable(tcx: TyCtxt<'tcx>) -> &'tcx QueryVTable<'tcx, Self::Cache>; } pub fn query_system<'tcx>( diff --git a/compiler/rustc_query_impl/src/plumbing.rs b/compiler/rustc_query_impl/src/plumbing.rs index 11077e8e0ee20..a25be9bb0156d 100644 --- a/compiler/rustc_query_impl/src/plumbing.rs +++ b/compiler/rustc_query_impl/src/plumbing.rs @@ -13,12 +13,13 @@ use rustc_middle::bug; #[expect(unused_imports, reason = "used by doc comments")] use rustc_middle::dep_graph::DepKindVTable; use rustc_middle::dep_graph::{DepKind, DepNode, DepNodeIndex, DepNodeKey, SerializedDepNodeIndex}; +use rustc_middle::query::erase::{Erasable, Erased}; use rustc_middle::query::on_disk_cache::{ AbsoluteBytePos, CacheDecoder, CacheEncoder, EncodedDepNodeIndex, }; use rustc_middle::query::plumbing::QueryVTable; use rustc_middle::query::{ - Key, QueryCache, QueryJobId, QueryStackDeferred, QueryStackFrame, QueryStackFrameExtra, + Key, QueryCache, QueryJobId, QueryStackDeferred, QueryStackFrame, QueryStackFrameExtra, erase, }; use rustc_middle::ty::codec::TyEncoder; use rustc_middle::ty::print::with_reduced_queries; @@ -27,7 +28,6 @@ use rustc_middle::ty::{self, TyCtxt}; use rustc_serialize::{Decodable, Encodable}; use rustc_span::def_id::LOCAL_CRATE; -use crate::QueryDispatcherUnerased; use crate::error::{QueryOverflow, QueryOverflowNote}; use crate::execution::{all_inactive, force_query}; use crate::job::{QueryJobMap, find_dep_kind_root}; @@ -324,14 +324,14 @@ where QueryStackFrame::new(info, kind, def_id, def_id_for_ty_in_cycle) } -pub(crate) fn encode_query_results<'a, 'tcx, Q, C: QueryCache>( - query: &'tcx QueryVTable<'tcx, C>, +pub(crate) fn encode_query_results_inner<'a, 'tcx, C, V>( tcx: TyCtxt<'tcx>, + query: &'tcx QueryVTable<'tcx, C>, encoder: &mut CacheEncoder<'a, 'tcx>, query_result_index: &mut EncodedDepNodeIndex, ) where - Q: QueryDispatcherUnerased<'tcx, C>, - Q::UnerasedValue: Encodable>, + C: QueryCache>, + V: Erasable + Encodable>, { let _timer = tcx.prof.generic_activity_with_arg("encode_query_results_for", query.name); @@ -346,7 +346,7 @@ pub(crate) fn encode_query_results<'a, 'tcx, Q, C: QueryCache>( // Encode the type check tables with the `SerializedDepNodeIndex` // as tag. - encoder.encode_tagged(dep_node, &Q::restore_val(*value)); + encoder.encode_tagged(dep_node, &erase::restore_val::(*value)); } }); } @@ -473,7 +473,6 @@ macro_rules! define_queries { pub(crate) mod query_impl { $(pub(crate) mod $name { use super::super::*; - use std::marker::PhantomData; use ::rustc_middle::query::erase::{self, Erased}; pub(crate) mod get_query_incr { @@ -607,29 +606,16 @@ macro_rules! define_queries { } } - #[derive(Copy, Clone, Default)] - pub(crate) struct QueryType<'tcx> { - data: PhantomData<&'tcx ()> - } + /// Marker type that implements [`GetQueryVTable`] for this query. + pub(crate) enum VTableGetter {} - impl<'tcx> QueryDispatcherUnerased<'tcx, queries::$name::Storage<'tcx>> - for QueryType<'tcx> - { - type UnerasedValue = queries::$name::Value<'tcx>; + impl<'tcx> GetQueryVTable<'tcx> for VTableGetter { + type Cache = rustc_middle::queries::$name::Storage<'tcx>; #[inline(always)] - fn query_vtable(tcx: TyCtxt<'tcx>) - -> &'tcx QueryVTable<'tcx, queries::$name::Storage<'tcx>> - { + fn query_vtable(tcx: TyCtxt<'tcx>) -> &'tcx QueryVTable<'tcx, Self::Cache> { &tcx.query_system.query_vtables.$name } - - #[inline(always)] - fn restore_val(value: as QueryCache>::Value) - -> Self::UnerasedValue - { - erase::restore_val::>(value) - } } /// Internal per-query plumbing for collecting the set of active jobs for this query. @@ -683,12 +669,9 @@ macro_rules! define_queries { encoder: &mut CacheEncoder<'_, 'tcx>, query_result_index: &mut EncodedDepNodeIndex ) { - $crate::plumbing::encode_query_results::< - query_impl::$name::QueryType<'tcx>, - _ - > ( - &tcx.query_system.query_vtables.$name, + $crate::plumbing::encode_query_results_inner( tcx, + &tcx.query_system.query_vtables.$name, encoder, query_result_index, ) @@ -773,8 +756,8 @@ macro_rules! define_queries { $( /// `DepKindVTable` constructor for this query. pub(crate) fn $name<'tcx>() -> DepKindVTable<'tcx> { - use $crate::query_impl::$name::QueryType; - make_dep_kind_vtable_for_query::, _>( + use $crate::query_impl::$name::VTableGetter; + make_dep_kind_vtable_for_query::( is_anon!([$($modifiers)*]), is_eval_always!([$($modifiers)*]), ) From f8b5f9c3dda7cbbef812b45d535cae35f2ee0df8 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Feb 2026 21:16:16 +0100 Subject: [PATCH 14/15] Port `#[register_tool]` to the new attribute parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jana Dönszelmann --- .../src/attributes/crate_level.rs | 47 +++++++++++++++++++ compiler/rustc_attr_parsing/src/context.rs | 1 + .../rustc_hir/src/attrs/data_structures.rs | 3 ++ .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_passes/src/check_attr.rs | 4 +- 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs index 176af5cdd192e..2d2994c02cd61 100644 --- a/compiler/rustc_attr_parsing/src/attributes/crate_level.rs +++ b/compiler/rustc_attr_parsing/src/attributes/crate_level.rs @@ -347,3 +347,50 @@ impl CombineAttributeParser for FeatureParser { res } } + +pub(crate) struct RegisterToolParser; + +impl CombineAttributeParser for RegisterToolParser { + const PATH: &[Symbol] = &[sym::register_tool]; + type Item = Ident; + const CONVERT: ConvertFn = AttributeKind::RegisterTool; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]); + + fn extend( + cx: &mut AcceptContext<'_, '_, S>, + args: &ArgParser, + ) -> impl IntoIterator { + let ArgParser::List(list) = args else { + cx.expected_list(cx.attr_span, args); + return Vec::new(); + }; + + if list.is_empty() { + cx.warn_empty_attribute(cx.attr_span); + } + + let mut res = Vec::new(); + + for elem in list.mixed() { + let Some(elem) = elem.meta_item() else { + cx.expected_identifier(elem.span()); + continue; + }; + if let Err(arg_span) = elem.args().no_args() { + cx.expected_no_args(arg_span); + continue; + } + + let path = elem.path(); + let Some(ident) = path.word() else { + cx.expected_identifier(path.span()); + continue; + }; + + res.push(ident); + } + + res + } +} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 3e5d9dc8fb6f5..cfb9c802a421f 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -160,6 +160,7 @@ attribute_parsers!( Combine, Combine, Combine, + Combine, Combine, Combine, Combine, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index e33d943fa5461..fe91ba4b3c247 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1139,6 +1139,9 @@ pub enum AttributeKind { /// Represents `#[reexport_test_harness_main]` ReexportTestHarnessMain(Symbol), + /// Represents `#[register_tool]` + RegisterTool(ThinVec, Span), + /// Represents [`#[repr]`](https://doc.rust-lang.org/stable/reference/type-layout.html#representations). Repr { reprs: ThinVec<(ReprAttr, Span)>, diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 3088d4bc32858..9e1302282c5ba 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -92,6 +92,7 @@ impl AttributeKind { ProfilerRuntime => No, RecursionLimit { .. } => No, ReexportTestHarnessMain(..) => No, + RegisterTool(..) => No, Repr { .. } => No, RustcAbi { .. } => No, RustcAllocator => No, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index af76ea183c476..4b569b49b9347 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -289,6 +289,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::ProfilerRuntime | AttributeKind::RecursionLimit { .. } | AttributeKind::ReexportTestHarnessMain(..) + | AttributeKind::RegisterTool(..) // handled below this loop and elsewhere | AttributeKind::Repr { .. } | AttributeKind::RustcAbi { .. } @@ -407,8 +408,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::rustc_layout | sym::rustc_autodiff // crate-level attrs, are checked below - | sym::feature - | sym::register_tool, + | sym::feature, .. ] => {} [name, rest@..] => { From 07bf6ae810dccfa1ccead870dc07af22adf2cd04 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Sun, 22 Feb 2026 21:16:39 +0100 Subject: [PATCH 15/15] Use the new parser throughout the compiler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jana Dönszelmann --- compiler/rustc_driver_impl/src/lib.rs | 3 +- compiler/rustc_resolve/src/errors.rs | 9 ---- compiler/rustc_resolve/src/macros.rs | 50 +++++++++++-------- tests/ui/tool-attributes/invalid-tool.rs | 2 +- tests/ui/tool-attributes/invalid-tool.stderr | 10 ++-- tests/ui/tool-attributes/nested-disallowed.rs | 2 +- .../tool-attributes/nested-disallowed.stderr | 10 ++-- 7 files changed, 46 insertions(+), 40 deletions(-) diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs index fc46102f6a30f..38a11b427c1f6 100644 --- a/compiler/rustc_driver_impl/src/lib.rs +++ b/compiler/rustc_driver_impl/src/lib.rs @@ -707,8 +707,9 @@ fn print_crate_info( }; let crate_name = passes::get_crate_name(sess, attrs); let lint_store = crate::unerased_lint_store(sess); - let registered_tools = rustc_resolve::registered_tools_ast(sess.dcx(), attrs); let features = rustc_expand::config::features(sess, attrs, crate_name); + let registered_tools = + rustc_resolve::registered_tools_ast(sess.dcx(), attrs, sess, &features); let lint_levels = rustc_lint::LintLevelsBuilder::crate_root( sess, &features, diff --git a/compiler/rustc_resolve/src/errors.rs b/compiler/rustc_resolve/src/errors.rs index 1ca5c17856262..45c1dee9bf52b 100644 --- a/compiler/rustc_resolve/src/errors.rs +++ b/compiler/rustc_resolve/src/errors.rs @@ -1201,15 +1201,6 @@ pub(crate) struct ToolWasAlreadyRegistered { pub(crate) old_ident_span: Span, } -#[derive(Diagnostic)] -#[diag("`{$tool}` only accepts identifiers")] -pub(crate) struct ToolOnlyAcceptsIdentifiers { - #[primary_span] - #[label("not an identifier")] - pub(crate) span: Span, - pub(crate) tool: Symbol, -} - #[derive(Subdiagnostic)] pub(crate) enum DefinedHere { #[label("similarly named {$candidate_descr} `{$candidate}` defined here")] diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 99cd485241546..551d89ee6022a 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -4,8 +4,9 @@ use std::mem; use std::sync::Arc; -use rustc_ast::{self as ast, Crate, NodeId, attr}; +use rustc_ast::{self as ast, Crate, DUMMY_NODE_ID, NodeId}; use rustc_ast_pretty::pprust; +use rustc_attr_parsing::AttributeParser; use rustc_errors::{Applicability, DiagCtxtHandle, StashKey}; use rustc_expand::base::{ Annotatable, DeriveResolution, Indeterminate, ResolverExpand, SyntaxExtension, @@ -15,12 +16,14 @@ use rustc_expand::compile_declarative_macro; use rustc_expand::expand::{ AstFragment, AstFragmentKind, Invocation, InvocationKind, SupportsMacroExpansion, }; -use rustc_hir::StabilityLevel; -use rustc_hir::attrs::{CfgEntry, StrippedCfgItem}; +use rustc_feature::Features; +use rustc_hir::attrs::{AttributeKind, CfgEntry, StrippedCfgItem}; use rustc_hir::def::{self, DefKind, MacroKinds, Namespace, NonMacroAttrKind}; use rustc_hir::def_id::{CrateNum, DefId, LocalDefId}; +use rustc_hir::{Attribute, StabilityLevel}; use rustc_middle::middle::stability; use rustc_middle::ty::{RegisteredTools, TyCtxt}; +use rustc_session::Session; use rustc_session::lint::builtin::{ LEGACY_DERIVE_HELPERS, OUT_OF_SCOPE_MACRO_CALLS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES, UNUSED_MACRO_RULES, UNUSED_MACROS, @@ -122,35 +125,38 @@ fn fast_print_path(path: &ast::Path) -> Symbol { pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools { let (_, pre_configured_attrs) = &*tcx.crate_for_resolver(()).borrow(); - registered_tools_ast(tcx.dcx(), pre_configured_attrs) + registered_tools_ast(tcx.dcx(), pre_configured_attrs, tcx.sess, tcx.features()) } pub fn registered_tools_ast( dcx: DiagCtxtHandle<'_>, pre_configured_attrs: &[ast::Attribute], + sess: &Session, + features: &Features, ) -> RegisteredTools { let mut registered_tools = RegisteredTools::default(); - for attr in attr::filter_by_name(pre_configured_attrs, sym::register_tool) { - for meta_item_inner in attr.meta_item_list().unwrap_or_default() { - match meta_item_inner.ident() { - Some(ident) => { - if let Some(old_ident) = registered_tools.replace(ident) { - dcx.emit_err(errors::ToolWasAlreadyRegistered { - span: ident.span, - tool: ident, - old_ident_span: old_ident.span, - }); - } - } - None => { - dcx.emit_err(errors::ToolOnlyAcceptsIdentifiers { - span: meta_item_inner.span(), - tool: sym::register_tool, - }); - } + + if let Some(Attribute::Parsed(AttributeKind::RegisterTool(tools, _))) = + AttributeParser::parse_limited( + sess, + pre_configured_attrs, + sym::register_tool, + DUMMY_SP, + DUMMY_NODE_ID, + Some(features), + ) + { + for tool in tools { + if let Some(old_tool) = registered_tools.replace(tool) { + dcx.emit_err(errors::ToolWasAlreadyRegistered { + span: tool.span, + tool, + old_ident_span: old_tool.span, + }); } } } + // We implicitly add `rustfmt`, `clippy`, `diagnostic`, `miri` and `rust_analyzer` to known // tools, but it's not an error to register them explicitly. let predefined_tools = diff --git a/tests/ui/tool-attributes/invalid-tool.rs b/tests/ui/tool-attributes/invalid-tool.rs index 125333231d217..aec31cc7f667c 100644 --- a/tests/ui/tool-attributes/invalid-tool.rs +++ b/tests/ui/tool-attributes/invalid-tool.rs @@ -1,6 +1,6 @@ #![feature(register_tool)] #![register_tool(1)] -//~^ ERROR `register_tool` only accepts identifiers +//~^ ERROR malformed `register_tool` attribute input fn main() {} diff --git a/tests/ui/tool-attributes/invalid-tool.stderr b/tests/ui/tool-attributes/invalid-tool.stderr index deafa6d167c20..4f82e9ef5437f 100644 --- a/tests/ui/tool-attributes/invalid-tool.stderr +++ b/tests/ui/tool-attributes/invalid-tool.stderr @@ -1,8 +1,12 @@ -error: `register_tool` only accepts identifiers - --> $DIR/invalid-tool.rs:3:18 +error[E0539]: malformed `register_tool` attribute input + --> $DIR/invalid-tool.rs:3:1 | LL | #![register_tool(1)] - | ^ not an identifier + | ^^^^^^^^^^^^^^^^^-^^ + | | | + | | expected a valid identifier here + | help: must be of the form: `#![register_tool(tool1, tool2, ...)]` error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0539`. diff --git a/tests/ui/tool-attributes/nested-disallowed.rs b/tests/ui/tool-attributes/nested-disallowed.rs index 8e78042776106..87d0bf48b6c8f 100644 --- a/tests/ui/tool-attributes/nested-disallowed.rs +++ b/tests/ui/tool-attributes/nested-disallowed.rs @@ -1,4 +1,4 @@ #![feature(register_tool)] -#![register_tool(foo::bar)] //~ ERROR only accepts identifiers +#![register_tool(foo::bar)] //~ ERROR malformed `register_tool` attribute input fn main() {} diff --git a/tests/ui/tool-attributes/nested-disallowed.stderr b/tests/ui/tool-attributes/nested-disallowed.stderr index 1af73fc2f1995..e59ebd979b4c6 100644 --- a/tests/ui/tool-attributes/nested-disallowed.stderr +++ b/tests/ui/tool-attributes/nested-disallowed.stderr @@ -1,8 +1,12 @@ -error: `register_tool` only accepts identifiers - --> $DIR/nested-disallowed.rs:2:18 +error[E0539]: malformed `register_tool` attribute input + --> $DIR/nested-disallowed.rs:2:1 | LL | #![register_tool(foo::bar)] - | ^^^^^^^^ not an identifier + | ^^^^^^^^^^^^^^^^^--------^^ + | | | + | | expected a valid identifier here + | help: must be of the form: `#![register_tool(tool1, tool2, ...)]` error: aborting due to 1 previous error +For more information about this error, try `rustc --explain E0539`.