From 011d95bca614a160f414a0d85a6cef9a49df503d Mon Sep 17 00:00:00 2001 From: Jamie Cunliffe Date: Tue, 27 Jan 2026 16:18:12 +0000 Subject: [PATCH 01/32] 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/32] 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/32] 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/32] 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/32] `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/32] `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/32] `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/32] 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/32] 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 94787c8646127e6afd4d2ab965e0bfaa703a175a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Fri, 23 Jan 2026 00:29:08 +0100 Subject: [PATCH 10/32] on_unimplemented: split structs and implementations --- compiler/rustc_hir/src/attrs/diagnostic.rs | 253 +++++ compiler/rustc_hir/src/attrs/mod.rs | 1 + .../rustc_hir_analysis/src/check/check.rs | 4 +- .../rustc_hir_typeck/src/method/suggest.rs | 3 +- .../traits/fulfillment_errors.rs | 10 +- .../traits/on_unimplemented.rs | 980 +++++++++--------- .../traits/on_unimplemented_condition.rs | 400 ++----- .../traits/on_unimplemented_format.rs | 237 ++--- 8 files changed, 936 insertions(+), 952 deletions(-) create mode 100644 compiler/rustc_hir/src/attrs/diagnostic.rs diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs new file mode 100644 index 0000000000000..0298b7684caf7 --- /dev/null +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -0,0 +1,253 @@ +//! Contains the data structures used by the diagnostic attribute family. + +use rustc_span::{DesugaringKind, Span, Symbol}; + +/// Represents a format string in a on_unimplemented attribute, +/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]` +#[derive(Clone, Debug)] +pub struct OnUnimplementedFormatString { + /// Symbol of the format string, i.e. `"content"` + pub symbol: Symbol, + /// The span of the format string, i.e. `"content"` + pub span: Span, + pub is_diagnostic_namespace_variant: bool, +} + +#[derive(Debug)] +pub struct OnUnimplementedDirective { + pub condition: Option, + pub subcommands: Vec, + pub message: Option<(Span, OnUnimplementedFormatString)>, + pub label: Option<(Span, OnUnimplementedFormatString)>, + pub notes: Vec, + pub parent_label: Option, + pub append_const_msg: Option, +} + +/// For the `#[rustc_on_unimplemented]` attribute +#[derive(Default, Debug)] +pub struct OnUnimplementedNote { + pub message: Option, + pub label: Option, + pub notes: Vec, + pub parent_label: Option, + // If none, should fall back to a generic message + pub append_const_msg: Option, +} + +/// Append a message for `[const] Trait` errors. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum AppendConstMessage { + #[default] + Default, + Custom(Symbol, Span), +} + +/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", +/// either as string pieces or dynamic arguments. +#[derive(Debug)] +pub struct FormatString { + pub input: Symbol, + pub span: Span, + pub pieces: Vec, + /// The formatting string was parsed successfully but with warnings + pub warnings: Vec, +} + +#[derive(Debug)] +pub enum Piece { + Lit(String), + Arg(FormatArg), +} + +#[derive(Debug)] +pub enum FormatArg { + // A generic parameter, like `{T}` if we're on the `From` trait. + GenericParam { + generic_param: Symbol, + }, + // `{Self}` + SelfUpper, + /// `{This}` or `{TraitName}` + This, + /// The sugared form of the trait + Trait, + /// what we're in, like a function, method, closure etc. + ItemContext, + /// What the user typed, if it doesn't match anything we can use. + AsIs(String), +} + +#[derive(Debug)] +pub enum FormatWarning { + UnknownParam { argument_name: Symbol, span: Span }, + PositionalArgument { span: Span, help: String }, + InvalidSpecifier { name: String, span: Span }, + FutureIncompat { span: Span, help: String }, +} + +/// Represents the `on` filter in `#[rustc_on_unimplemented]`. +#[derive(Debug)] +pub struct OnUnimplementedCondition { + pub span: Span, + pub pred: Predicate, +} + +/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`]. +/// +/// It is similar to the predicate in the `cfg` attribute, +/// and may contain nested predicates. +#[derive(Debug)] +pub enum Predicate { + /// A condition like `on(crate_local)`. + Flag(Flag), + /// A match, like `on(Rhs = "Whatever")`. + Match(NameValue), + /// Negation, like `on(not($pred))`. + Not(Box), + /// True if all predicates are true, like `on(all($a, $b, $c))`. + All(Vec), + /// True if any predicate is true, like `on(any($a, $b, $c))`. + Any(Vec), +} + +impl Predicate { + pub fn eval(&self, eval: &mut impl FnMut(FlagOrNv<'_>) -> bool) -> bool { + match self { + Predicate::Flag(flag) => eval(FlagOrNv::Flag(flag)), + Predicate::Match(nv) => eval(FlagOrNv::NameValue(nv)), + Predicate::Not(not) => !not.eval(eval), + Predicate::All(preds) => preds.into_iter().all(|pred| pred.eval(eval)), + Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)), + } + } +} + +/// Represents a `MetaWord` in an `on`-filter. +#[derive(Debug, Clone, Copy)] +pub enum Flag { + /// Whether the code causing the trait bound to not be fulfilled + /// is part of the user's crate. + CrateLocal, + /// Whether the obligation is user-specified rather than derived. + Direct, + /// Whether we are in some kind of desugaring like + /// `?` or `try { .. }`. + FromDesugaring, +} + +/// A `MetaNameValueStr` in an `on`-filter. +/// +/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`. +#[derive(Debug, Clone)] +pub struct NameValue { + pub name: Name, + /// Something like `"&str"` or `"alloc::string::String"`, + /// in which case it just contains a single string piece. + /// But if it is something like `"&[{A}]"` then it must be formatted later. + pub value: FilterFormatString, +} + +/// The valid names of the `on` filter. +#[derive(Debug, Clone, Copy)] +pub enum Name { + Cause, + FromDesugaring, + SelfUpper, + GenericArg(Symbol), +} + +#[derive(Debug, Clone)] +pub enum FlagOrNv<'p> { + Flag(&'p Flag), + NameValue(&'p NameValue), +} + +/// Represents a value inside an `on` filter. +/// +/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`. +/// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`. +/// The `Arg` variant is used when it contains formatting like +/// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`. +#[derive(Debug, Clone)] +pub struct FilterFormatString { + pub pieces: Vec, +} + +#[derive(Debug, Clone)] +pub enum LitOrArg { + Lit(String), + Arg(String), +} + +/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the +/// [`OnUnimplementedCondition`]. +/// +/// For example, given a +/// ```rust,ignore (just an example) +/// #[rustc_on_unimplemented( +/// on(all(from_desugaring = "QuestionMark"), +/// message = "the `?` operator can only be used in {ItemContext} \ +/// that returns `Result` or `Option` \ +/// (or another type that implements `{FromResidual}`)", +/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", +/// parent_label = "this function should return `Result` or `Option` to accept `?`" +/// ), +/// )] +/// pub trait FromResidual::Residual> { +/// ... +/// } +/// +/// async fn an_async_function() -> u32 { +/// let x: Option = None; +/// x?; //~ ERROR the `?` operator +/// 22 +/// } +/// ``` +/// it will look like this: +/// +/// ```rust,ignore (just an example) +/// ConditionOptions { +/// self_types: ["u32", "{integral}"], +/// from_desugaring: Some("QuestionMark"), +/// cause: None, +/// crate_local: false, +/// direct: true, +/// generic_args: [("Self","u32"), +/// ("R", "core::option::Option"), +/// ("R", "core::option::Option" ), +/// ], +/// } +/// ``` +#[derive(Debug)] +pub struct ConditionOptions { + /// All the self types that may apply. + pub self_types: Vec, + // The kind of compiler desugaring. + pub from_desugaring: Option, + /// Match on a variant of [rustc_infer::traits::ObligationCauseCode]. + pub cause: Option, + pub crate_local: bool, + /// Is the obligation "directly" user-specified, rather than derived? + pub direct: bool, + // A list of the generic arguments and their reified types. + pub generic_args: Vec<(Symbol, String)>, +} + +impl ConditionOptions { + pub fn has_flag(&self, name: Flag) -> bool { + match name { + Flag::CrateLocal => self.crate_local, + Flag::Direct => self.direct, + Flag::FromDesugaring => self.from_desugaring.is_some(), + } + } + pub fn contains(&self, name: Name, value: String) -> bool { + match name { + Name::SelfUpper => self.self_types.contains(&value), + Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)), + Name::Cause => self.cause == Some(value), + Name::GenericArg(arg) => self.generic_args.contains(&(arg, value)), + } + } +} diff --git a/compiler/rustc_hir/src/attrs/mod.rs b/compiler/rustc_hir/src/attrs/mod.rs index 7e3ac666d0f50..09fa144a16041 100644 --- a/compiler/rustc_hir/src/attrs/mod.rs +++ b/compiler/rustc_hir/src/attrs/mod.rs @@ -9,6 +9,7 @@ pub use encode_cross_crate::EncodeCrossCrate; pub use pretty_printing::PrintAttribute; mod data_structures; +pub mod diagnostic; mod encode_cross_crate; mod pretty_printing; diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 349ad4f7fc43b..8e0ec9e489cf6 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -26,7 +26,7 @@ use rustc_session::lint::builtin::UNINHABITED_STATIC; use rustc_span::source_map::Spanned; use rustc_target::spec::{AbiMap, AbiMapping}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedDirective; +use rustc_trait_selection::error_reporting::traits::on_unimplemented::of_item_directive; use rustc_trait_selection::traits; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use tracing::{debug, instrument}; @@ -1124,7 +1124,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), pub(super) fn check_diagnostic_attrs(tcx: TyCtxt<'_>, def_id: LocalDefId) { // an error would be reported if this fails. - let _ = OnUnimplementedDirective::of_item(tcx, def_id.to_def_id()); + let _ = of_item_directive(tcx, def_id.to_def_id()); } pub(super) fn check_specialization_validity<'tcx>( diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 862a51288730d..1f496bc5ce270 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -16,6 +16,8 @@ use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, MultiSpan, StashKey, listify, pluralize, struct_span_code_err, }; +use rustc_hir::attrs::AttributeKind; +use rustc_hir::attrs::diagnostic::OnUnimplementedNote; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{self, Visitor}; @@ -37,7 +39,6 @@ use rustc_span::{ kw, sym, }; use rustc_trait_selection::error_reporting::traits::DefIdOrName; -use rustc_trait_selection::error_reporting::traits::on_unimplemented::OnUnimplementedNote; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use rustc_trait_selection::traits::{ diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 983890f6b48db..3e367320a0bfb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -14,6 +14,7 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions, msg, pluralize, struct_span_code_err, }; +use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedNote}; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::intravisit::Visitor; use rustc_hir::{self as hir, LangItem, Node}; @@ -37,14 +38,13 @@ use rustc_span::def_id::CrateNum; use rustc_span::{BytePos, DUMMY_SP, STDLIB_STABLE_CRATES, Span, Symbol, sym}; use tracing::{debug, instrument}; -use super::on_unimplemented::{AppendConstMessage, OnUnimplementedNote}; use super::suggestions::get_explanation_based_on_obligation; use super::{ ArgKind, CandidateSimilarity, FindExprBySpan, GetSafeTransmuteErrorAndReason, ImplCandidate, }; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::infer::TyCategory; -use crate::error_reporting::traits::on_unimplemented::OnUnimplementedDirective; +use crate::error_reporting::traits::on_unimplemented::{evaluate_directive, of_item_directive}; use crate::error_reporting::traits::report_dyn_incompatibility; use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch, CoroClosureNotFn}; use crate::infer::{self, InferCtxt, InferCtxtExt as _}; @@ -912,9 +912,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { diag.long_ty_path(), ); - if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, impl_did) - { - let note = command.evaluate( + if let Ok(Some(command)) = of_item_directive(self.tcx, impl_did) { + let note = evaluate_directive( + &command, self.tcx, predicate.skip_binder().trait_ref, &condition_options, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 0af7b0e3253dd..b1fdfa1b42632 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -5,6 +5,10 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_errors::codes::*; use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_hir as hir; +use rustc_hir::attrs::diagnostic::{ + AppendConstMessage, ConditionOptions, FormatString, FormatWarning, OnUnimplementedDirective, + OnUnimplementedFormatString, OnUnimplementedNote, +}; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::{AttrArgs, Attribute}; @@ -21,14 +25,11 @@ use tracing::{debug, info}; use super::{ObligationCauseCode, PredicateObligation}; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::traits::on_unimplemented_condition::{ - ConditionOptions, OnUnimplementedCondition, -}; -use crate::error_reporting::traits::on_unimplemented_format::{ - Ctx, FormatArgs, FormatString, FormatWarning, + matches_predicate, parse_condition, }; +use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs}; use crate::errors::{InvalidOnClause, NoValueInOnUnimplemented}; use crate::infer::InferCtxtExt; - impl<'tcx> TypeErrCtxt<'_, 'tcx> { fn impl_similar_to( &self, @@ -107,9 +108,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } let (condition_options, format_args) = self.on_unimplemented_components(trait_pred, obligation, long_ty_path); - if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, trait_pred.def_id()) - { - command.evaluate( + if let Ok(Some(command)) = of_item_directive(self.tcx, trait_pred.def_id()) { + evaluate_directive( + &command, self.tcx, trait_pred.skip_binder().trait_ref, &condition_options, @@ -319,47 +320,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } -/// Represents a format string in a on_unimplemented attribute, -/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]` -#[derive(Clone, Debug)] -pub struct OnUnimplementedFormatString { - /// Symbol of the format string, i.e. `"content"` - symbol: Symbol, - /// The span of the format string, i.e. `"content"` - span: Span, - is_diagnostic_namespace_variant: bool, -} - -#[derive(Debug)] -pub struct OnUnimplementedDirective { - condition: Option, - subcommands: Vec, - message: Option<(Span, OnUnimplementedFormatString)>, - label: Option<(Span, OnUnimplementedFormatString)>, - notes: Vec, - parent_label: Option, - append_const_msg: Option, -} - -/// For the `#[rustc_on_unimplemented]` attribute -#[derive(Default, Debug)] -pub struct OnUnimplementedNote { - pub message: Option, - pub label: Option, - pub notes: Vec, - pub parent_label: Option, - // If none, should fall back to a generic message - pub append_const_msg: Option, -} - -/// Append a message for `[const] Trait` errors. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] -pub enum AppendConstMessage { - #[default] - Default, - Custom(Symbol, Span), -} - #[derive(LintDiagnostic)] #[diag("malformed `on_unimplemented` attribute")] #[help("only `message`, `note` and `label` are allowed as options")] @@ -417,518 +377,512 @@ pub struct WrappedParserError { pub label: String, } -impl<'tcx> OnUnimplementedDirective { - fn parse( - tcx: TyCtxt<'tcx>, - item_def_id: DefId, - items: &[MetaItemInner], - span: Span, - is_root: bool, - is_diagnostic_namespace_variant: bool, - ) -> Result, ErrorGuaranteed> { - let mut errored = None; - let mut item_iter = items.iter(); - - let parse_value = |value_str, span| { - OnUnimplementedFormatString::try_parse( - tcx, - item_def_id, - value_str, - span, - is_diagnostic_namespace_variant, - ) +fn parse_directive<'tcx>( + tcx: TyCtxt<'tcx>, + item_def_id: DefId, + items: &[MetaItemInner], + span: Span, + is_root: bool, + is_diagnostic_namespace_variant: bool, +) -> Result, ErrorGuaranteed> { + let mut errored = None; + let mut item_iter = items.iter(); + + let parse_value = |value_str, span| { + try_parse_format_string(tcx, item_def_id, value_str, span, is_diagnostic_namespace_variant) .map(Some) - }; + }; - let condition = if is_root { - None + let condition = if is_root { + None + } else { + let cond = + item_iter.next().ok_or_else(|| tcx.dcx().emit_err(InvalidOnClause::Empty { span }))?; + + let generics: Vec = tcx + .generics_of(item_def_id) + .own_params + .iter() + .filter_map(|param| { + if matches!(param.kind, GenericParamDefKind::Lifetime) { + None + } else { + Some(param.name) + } + }) + .collect(); + match parse_condition(cond, &generics) { + Ok(condition) => Some(condition), + Err(e) => return Err(tcx.dcx().emit_err(e)), + } + }; + + let mut message = None; + let mut label = None; + let mut notes = Vec::new(); + let mut parent_label = None; + let mut subcommands = vec![]; + let mut append_const_msg = None; + + let get_value_and_span = |item: &_, key| { + if let MetaItemInner::MetaItem(MetaItem { + path, + kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }), + .. + }) = item + && *path == key + { + Some((*s, *span)) } else { - let cond = item_iter - .next() - .ok_or_else(|| tcx.dcx().emit_err(InvalidOnClause::Empty { span }))?; - - let generics: Vec = tcx - .generics_of(item_def_id) - .own_params - .iter() - .filter_map(|param| { - if matches!(param.kind, GenericParamDefKind::Lifetime) { - None - } else { - Some(param.name) - } - }) - .collect(); - match OnUnimplementedCondition::parse(cond, &generics) { - Ok(condition) => Some(condition), - Err(e) => return Err(tcx.dcx().emit_err(e)), - } - }; + None + } + }; - let mut message = None; - let mut label = None; - let mut notes = Vec::new(); - let mut parent_label = None; - let mut subcommands = vec![]; - let mut append_const_msg = None; - - let get_value_and_span = |item: &_, key| { - if let MetaItemInner::MetaItem(MetaItem { - path, - kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }), - .. - }) = item - && *path == key - { - Some((*s, *span)) - } else { - None + for item in item_iter { + if let Some((message_, span)) = get_value_and_span(item, sym::message) + && message.is_none() + { + message = parse_value(message_, span)?.map(|l| (item.span(), l)); + continue; + } else if let Some((label_, span)) = get_value_and_span(item, sym::label) + && label.is_none() + { + label = parse_value(label_, span)?.map(|l| (item.span(), l)); + continue; + } else if let Some((note_, span)) = get_value_and_span(item, sym::note) { + if let Some(note) = parse_value(note_, span)? { + notes.push(note); + continue; } - }; - - for item in item_iter { - if let Some((message_, span)) = get_value_and_span(item, sym::message) - && message.is_none() - { - message = parse_value(message_, span)?.map(|l| (item.span(), l)); + } else if item.has_name(sym::parent_label) + && parent_label.is_none() + && !is_diagnostic_namespace_variant + { + if let Some(parent_label_) = item.value_str() { + parent_label = parse_value(parent_label_, item.span())?; continue; - } else if let Some((label_, span)) = get_value_and_span(item, sym::label) - && label.is_none() - { - label = parse_value(label_, span)?.map(|l| (item.span(), l)); + } + } else if item.has_name(sym::on) + && is_root + && message.is_none() + && label.is_none() + && notes.is_empty() + && !is_diagnostic_namespace_variant + // FIXME(diagnostic_namespace): disallow filters for now + { + if let Some(items) = item.meta_item_list() { + match parse_directive( + tcx, + item_def_id, + items, + item.span(), + false, + is_diagnostic_namespace_variant, + ) { + Ok(Some(subcommand)) => subcommands.push(subcommand), + Ok(None) => bug!( + "This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false" + ), + Err(reported) => errored = Some(reported), + }; continue; - } else if let Some((note_, span)) = get_value_and_span(item, sym::note) { - if let Some(note) = parse_value(note_, span)? { - notes.push(note); - continue; - } - } else if item.has_name(sym::parent_label) - && parent_label.is_none() - && !is_diagnostic_namespace_variant - { - if let Some(parent_label_) = item.value_str() { - parent_label = parse_value(parent_label_, item.span())?; - continue; - } - } else if item.has_name(sym::on) - && is_root - && message.is_none() - && label.is_none() - && notes.is_empty() - && !is_diagnostic_namespace_variant - // FIXME(diagnostic_namespace): disallow filters for now - { - if let Some(items) = item.meta_item_list() { - match Self::parse( - tcx, - item_def_id, - items, - item.span(), - false, - is_diagnostic_namespace_variant, - ) { - Ok(Some(subcommand)) => subcommands.push(subcommand), - Ok(None) => bug!( - "This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false" - ), - Err(reported) => errored = Some(reported), - }; - continue; - } - } else if item.has_name(sym::append_const_msg) - && append_const_msg.is_none() - && !is_diagnostic_namespace_variant - { - if let Some(msg) = item.value_str() { - append_const_msg = Some(AppendConstMessage::Custom(msg, item.span())); - continue; - } else if item.is_word() { - append_const_msg = Some(AppendConstMessage::Default); - continue; - } } - - if is_diagnostic_namespace_variant { - if let Some(def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(def_id), - vec![item.span()], - MalformedOnUnimplementedAttrLint::new(item.span()), - ); - } - } else { - // nothing found - tcx.dcx().emit_err(NoValueInOnUnimplemented { span: item.span() }); + } else if item.has_name(sym::append_const_msg) + && append_const_msg.is_none() + && !is_diagnostic_namespace_variant + { + if let Some(msg) = item.value_str() { + append_const_msg = Some(AppendConstMessage::Custom(msg, item.span())); + continue; + } else if item.is_word() { + append_const_msg = Some(AppendConstMessage::Default); + continue; } } - if let Some(reported) = errored { - if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) } + if is_diagnostic_namespace_variant { + if let Some(def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + tcx.local_def_id_to_hir_id(def_id), + vec![item.span()], + MalformedOnUnimplementedAttrLint::new(item.span()), + ); + } } else { - Ok(Some(OnUnimplementedDirective { - condition, - subcommands, - message, - label, - notes, - parent_label, - append_const_msg, - })) + // nothing found + tcx.dcx().emit_err(NoValueInOnUnimplemented { span: item.span() }); } } - pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result, ErrorGuaranteed> { - let attr = if tcx.is_trait(item_def_id) { - sym::on_unimplemented - } else if let DefKind::Impl { of_trait: true } = tcx.def_kind(item_def_id) { - sym::on_const - } else { - // It could be a trait_alias (`trait MyTrait = SomeOtherTrait`) - // or an implementation (`impl MyTrait for Foo {}`) - // - // We don't support those. - return Ok(None); - }; - if let Some(attr) = { - #[allow(deprecated)] - tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) - } { - return Self::parse_attribute(attr, false, tcx, item_def_id); - } else { - tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) - .filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose()) - .try_fold(None, |aggr: Option, directive| { - let directive = directive?; - if let Some(aggr) = aggr { - let mut subcommands = aggr.subcommands; - subcommands.extend(directive.subcommands); - let mut notes = aggr.notes; - notes.extend(directive.notes); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.message.as_ref().map(|f| f.0), - aggr.message.as_ref().map(|f| f.0), - "message", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.label.as_ref().map(|f| f.0), - aggr.label.as_ref().map(|f| f.0), - "label", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.condition.as_ref().map(|i| i.span()), - aggr.condition.as_ref().map(|i| i.span()), - "condition", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.parent_label.as_ref().map(|f| f.span), - aggr.parent_label.as_ref().map(|f| f.span), - "parent_label", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.append_const_msg.as_ref().and_then(|c| { - if let AppendConstMessage::Custom(_, s) = c { - Some(*s) - } else { - None - } - }), - aggr.append_const_msg.as_ref().and_then(|c| { - if let AppendConstMessage::Custom(_, s) = c { - Some(*s) - } else { - None - } - }), - "append_const_msg", - ); - - Ok(Some(Self { - condition: aggr.condition.or(directive.condition), - subcommands, - message: aggr.message.or(directive.message), - label: aggr.label.or(directive.label), - notes, - parent_label: aggr.parent_label.or(directive.parent_label), - append_const_msg: aggr.append_const_msg.or(directive.append_const_msg), - })) - } else { - Ok(Some(directive)) - } - }) - } + if let Some(reported) = errored { + if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) } + } else { + Ok(Some(OnUnimplementedDirective { + condition, + subcommands, + message, + label, + notes, + parent_label, + append_const_msg, + })) } +} - fn parse_attribute( - attr: &Attribute, - is_diagnostic_namespace_variant: bool, - tcx: TyCtxt<'tcx>, - item_def_id: DefId, - ) -> Result, ErrorGuaranteed> { - let result = if let Some(items) = attr.meta_item_list() { - Self::parse( - tcx, - item_def_id, - &items, - attr.span(), - true, - is_diagnostic_namespace_variant, - ) - } else if let Some(value) = attr.value_str() { - if !is_diagnostic_namespace_variant { - Ok(Some(OnUnimplementedDirective { - condition: None, - message: None, - subcommands: vec![], - label: Some(( - attr.span(), - OnUnimplementedFormatString::try_parse( - tcx, - item_def_id, - value, - attr.value_span().unwrap_or(attr.span()), - is_diagnostic_namespace_variant, - )?, - )), - notes: Vec::new(), - parent_label: None, - append_const_msg: None, - })) - } else { - let item = attr.get_normal_item(); - let report_span = match &item.args { - AttrArgs::Empty => item.path.span, - AttrArgs::Delimited(args) => args.dspan.entire(), - AttrArgs::Eq { eq_span, expr } => eq_span.to(expr.span), - }; +pub fn of_item_directive<'tcx>( + tcx: TyCtxt<'tcx>, + item_def_id: DefId, +) -> Result, ErrorGuaranteed> { + let attr = if tcx.is_trait(item_def_id) { + sym::on_unimplemented + } else if let DefKind::Impl { of_trait: true } = tcx.def_kind(item_def_id) { + sym::on_const + } else { + // It could be a trait_alias (`trait MyTrait = SomeOtherTrait`) + // or an implementation (`impl MyTrait for Foo {}`) + // + // We don't support those. + return Ok(None); + }; + if let Some(attr) = { + #[allow(deprecated)] + tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) + } { + return parse_attribute_directive(attr, false, tcx, item_def_id); + } else { + tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) + .filter_map(|attr| parse_attribute_directive(attr, true, tcx, item_def_id).transpose()) + .try_fold(None, |aggr: Option, directive| { + let directive = directive?; + if let Some(aggr) = aggr { + let mut subcommands = aggr.subcommands; + subcommands.extend(directive.subcommands); + let mut notes = aggr.notes; + notes.extend(directive.notes); + IgnoredDiagnosticOption::maybe_emit_warning( + tcx, + item_def_id, + directive.message.as_ref().map(|f| f.0), + aggr.message.as_ref().map(|f| f.0), + "message", + ); + IgnoredDiagnosticOption::maybe_emit_warning( + tcx, + item_def_id, + directive.label.as_ref().map(|f| f.0), + aggr.label.as_ref().map(|f| f.0), + "label", + ); + IgnoredDiagnosticOption::maybe_emit_warning( + tcx, + item_def_id, + directive.condition.as_ref().map(|i| i.span), + aggr.condition.as_ref().map(|i| i.span), + "condition", + ); + IgnoredDiagnosticOption::maybe_emit_warning( + tcx, + item_def_id, + directive.parent_label.as_ref().map(|f| f.span), + aggr.parent_label.as_ref().map(|f| f.span), + "parent_label", + ); + IgnoredDiagnosticOption::maybe_emit_warning( + tcx, + item_def_id, + directive.append_const_msg.as_ref().and_then(|c| { + if let AppendConstMessage::Custom(_, s) = c { Some(*s) } else { None } + }), + aggr.append_const_msg.as_ref().and_then(|c| { + if let AppendConstMessage::Custom(_, s) = c { Some(*s) } else { None } + }), + "append_const_msg", + ); + Ok(Some(OnUnimplementedDirective { + condition: aggr.condition.or(directive.condition), + subcommands, + message: aggr.message.or(directive.message), + label: aggr.label.or(directive.label), + notes, + parent_label: aggr.parent_label.or(directive.parent_label), + append_const_msg: aggr.append_const_msg.or(directive.append_const_msg), + })) + } else { + Ok(Some(directive)) + } + }) + } +} + +fn parse_attribute_directive<'tcx>( + attr: &Attribute, + is_diagnostic_namespace_variant: bool, + tcx: TyCtxt<'tcx>, + item_def_id: DefId, +) -> Result, ErrorGuaranteed> { + let result = if let Some(items) = attr.meta_item_list() { + parse_directive( + tcx, + item_def_id, + &items, + attr.span(), + true, + is_diagnostic_namespace_variant, + ) + } else if let Some(value) = attr.value_str() { + if !is_diagnostic_namespace_variant { + Ok(Some(OnUnimplementedDirective { + condition: None, + message: None, + subcommands: vec![], + label: Some(( + attr.span(), + try_parse_format_string( + tcx, + item_def_id, + value, + attr.value_span().unwrap_or(attr.span()), + is_diagnostic_namespace_variant, + )?, + )), + notes: Vec::new(), + parent_label: None, + append_const_msg: None, + })) + } else { + let item = attr.get_normal_item(); + let report_span = match &item.args { + AttrArgs::Empty => item.path.span, + AttrArgs::Delimited(args) => args.dspan.entire(), + AttrArgs::Eq { eq_span, expr } => eq_span.to(expr.span), + }; + + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + tcx.local_def_id_to_hir_id(item_def_id), + report_span, + MalformedOnUnimplementedAttrLint::new(report_span), + ); + } + Ok(None) + } + } else if is_diagnostic_namespace_variant { + match attr { + Attribute::Unparsed(p) if !matches!(p.args, AttrArgs::Empty) => { if let Some(item_def_id) = item_def_id.as_local() { tcx.emit_node_span_lint( MALFORMED_DIAGNOSTIC_ATTRIBUTES, tcx.local_def_id_to_hir_id(item_def_id), - report_span, - MalformedOnUnimplementedAttrLint::new(report_span), + attr.span(), + MalformedOnUnimplementedAttrLint::new(attr.span()), ); } - Ok(None) } - } else if is_diagnostic_namespace_variant { - match attr { - Attribute::Unparsed(p) if !matches!(p.args, AttrArgs::Empty) => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - attr.span(), - MalformedOnUnimplementedAttrLint::new(attr.span()), - ); - } - } - _ => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - attr.span(), - MissingOptionsForOnUnimplementedAttr, - ) - } + _ => { + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + tcx.local_def_id_to_hir_id(item_def_id), + attr.span(), + MissingOptionsForOnUnimplementedAttr, + ) } - }; - - Ok(None) - } else { - let reported = tcx.dcx().delayed_bug("of_item: neither meta_item_list nor value_str"); - return Err(reported); + } }; - debug!("of_item({:?}) = {:?}", item_def_id, result); - result - } - pub(crate) fn evaluate( - &self, - tcx: TyCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, - condition_options: &ConditionOptions, - args: &FormatArgs<'tcx>, - ) -> OnUnimplementedNote { - let mut message = None; - let mut label = None; - let mut notes = Vec::new(); - let mut parent_label = None; - let mut append_const_msg = None; - info!( - "evaluate({:?}, trait_ref={:?}, options={:?}, args ={:?})", - self, trait_ref, condition_options, args - ); - - for command in self.subcommands.iter().chain(Some(self)).rev() { - debug!(?command); - if let Some(ref condition) = command.condition - && !condition.matches_predicate(condition_options) - { - debug!("evaluate: skipping {:?} due to condition", command); - continue; - } - debug!("evaluate: {:?} succeeded", command); - if let Some(ref message_) = command.message { - message = Some(message_.clone()); - } + Ok(None) + } else { + let reported = + tcx.dcx().delayed_bug("of_item_directive: neither meta_item_list nor value_str"); + return Err(reported); + }; + debug!("of_item_directive({:?}) = {:?}", item_def_id, result); + result +} - if let Some(ref label_) = command.label { - label = Some(label_.clone()); - } +pub(crate) fn evaluate_directive<'tcx>( + slf: &OnUnimplementedDirective, + tcx: TyCtxt<'tcx>, + trait_ref: ty::TraitRef<'tcx>, + condition_options: &ConditionOptions, + args: &FormatArgs<'tcx>, +) -> OnUnimplementedNote { + let mut message = None; + let mut label = None; + let mut notes = Vec::new(); + let mut parent_label = None; + let mut append_const_msg = None; + info!( + "evaluate_directive({:?}, trait_ref={:?}, options={:?}, args ={:?})", + slf, trait_ref, condition_options, args + ); + + for command in slf.subcommands.iter().chain(Some(slf)).rev() { + debug!(?command); + if let Some(ref condition) = command.condition + && !matches_predicate(condition, condition_options) + { + debug!("evaluate_directive: skipping {:?} due to condition", command); + continue; + } + debug!("evaluate_directive: {:?} succeeded", command); + if let Some(ref message_) = command.message { + message = Some(message_.clone()); + } - notes.extend(command.notes.clone()); + if let Some(ref label_) = command.label { + label = Some(label_.clone()); + } - if let Some(ref parent_label_) = command.parent_label { - parent_label = Some(parent_label_.clone()); - } + notes.extend(command.notes.clone()); - append_const_msg = command.append_const_msg; + if let Some(ref parent_label_) = command.parent_label { + parent_label = Some(parent_label_.clone()); } - OnUnimplementedNote { - label: label.map(|l| l.1.format(tcx, trait_ref, args)), - message: message.map(|m| m.1.format(tcx, trait_ref, args)), - notes: notes.into_iter().map(|n| n.format(tcx, trait_ref, args)).collect(), - parent_label: parent_label.map(|e_s| e_s.format(tcx, trait_ref, args)), - append_const_msg, - } + append_const_msg = command.append_const_msg; } -} -impl<'tcx> OnUnimplementedFormatString { - fn try_parse( - tcx: TyCtxt<'tcx>, - item_def_id: DefId, - from: Symbol, - span: Span, - is_diagnostic_namespace_variant: bool, - ) -> Result { - let result = - OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant }; - result.verify(tcx, item_def_id)?; - Ok(result) + OnUnimplementedNote { + label: label.map(|l| format_directive(&l.1, tcx, trait_ref, args)), + message: message.map(|m| format_directive(&m.1, tcx, trait_ref, args)), + notes: notes.into_iter().map(|n| format_directive(&n, tcx, trait_ref, args)).collect(), + parent_label: parent_label.map(|e_s| format_directive(&e_s, tcx, trait_ref, args)), + append_const_msg, } +} - fn verify(&self, tcx: TyCtxt<'tcx>, trait_def_id: DefId) -> Result<(), ErrorGuaranteed> { - if !tcx.is_trait(trait_def_id) { - return Ok(()); - }; - - let ctx = if self.is_diagnostic_namespace_variant { - Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } - } else { - Ctx::RustcOnUnimplemented { tcx, trait_def_id } - }; - - let mut result = Ok(()); +fn try_parse_format_string<'tcx>( + tcx: TyCtxt<'tcx>, + item_def_id: DefId, + from: Symbol, + span: Span, + is_diagnostic_namespace_variant: bool, +) -> Result { + let result = + OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant }; + verify(&result, tcx, item_def_id)?; + Ok(result) +} - let snippet = tcx.sess.source_map().span_to_snippet(self.span).ok(); - match FormatString::parse(self.symbol, snippet, self.span, &ctx) { - // Warnings about format specifiers, deprecated parameters, wrong parameters etc. - // In other words we'd like to let the author know, but we can still try to format the string later - Ok(FormatString { warnings, .. }) => { - if self.is_diagnostic_namespace_variant { - for w in warnings { - w.emit_warning(tcx, trait_def_id) - } - } else { - for w in warnings { - match w { - FormatWarning::UnknownParam { argument_name, span } => { - let reported = struct_span_code_err!( - tcx.dcx(), - span, - E0230, - "cannot find parameter {} on this trait", - argument_name, - ) - .emit(); - result = Err(reported); - } - FormatWarning::PositionalArgument { span, .. } => { - let reported = struct_span_code_err!( - tcx.dcx(), - span, - E0231, - "positional format arguments are not allowed here" - ) - .emit(); - result = Err(reported); - } - FormatWarning::InvalidSpecifier { .. } - | FormatWarning::FutureIncompat { .. } => {} +fn verify<'tcx>( + slf: &OnUnimplementedFormatString, + tcx: TyCtxt<'tcx>, + trait_def_id: DefId, +) -> Result<(), ErrorGuaranteed> { + if !tcx.is_trait(trait_def_id) { + return Ok(()); + }; + + let ctx = if slf.is_diagnostic_namespace_variant { + Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } + } else { + Ctx::RustcOnUnimplemented { tcx, trait_def_id } + }; + + let mut result = Ok(()); + + use crate::error_reporting::traits::on_unimplemented_format::parse_format_string; + + let snippet = tcx.sess.source_map().span_to_snippet(slf.span).ok(); + match parse_format_string(slf.symbol, snippet, slf.span, &ctx) { + // Warnings about format specifiers, deprecated parameters, wrong parameters etc. + // In other words we'd like to let the author know, but we can still try to format the string later + Ok(FormatString { warnings, .. }) => { + if slf.is_diagnostic_namespace_variant { + for w in warnings { + use crate::error_reporting::traits::on_unimplemented_format::emit_warning; + + emit_warning(&w, tcx, trait_def_id) + } + } else { + for w in warnings { + match w { + FormatWarning::UnknownParam { argument_name, span } => { + let reported = struct_span_code_err!( + tcx.dcx(), + span, + E0230, + "cannot find parameter {} on this trait", + argument_name + ) + .emit(); + result = Err(reported); + } + FormatWarning::PositionalArgument { span, .. } => { + let reported = struct_span_code_err!( + tcx.dcx(), + span, + E0231, + "positional format arguments are not allowed here" + ) + .emit(); + result = Err(reported); } + FormatWarning::InvalidSpecifier { .. } + | FormatWarning::FutureIncompat { .. } => {} } } } - // Error from the underlying `rustc_parse_format::Parser` - Err(e) => { - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing we nevertheless want to show it as warning - // so that users are aware that something is not correct - if self.is_diagnostic_namespace_variant { - if let Some(trait_def_id) = trait_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(trait_def_id), - self.span, - WrappedParserError { description: e.description, label: e.label }, - ); - } - } else { - let reported = - struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description) - .emit(); - result = Err(reported); + } + // Error from the underlying `rustc_parse_format::Parser` + Err(e) => { + // we cannot return errors from processing the format string as hard error here + // as the diagnostic namespace guarantees that malformed input cannot cause an error + // + // if we encounter any error while processing we nevertheless want to show it as warning + // so that users are aware that something is not correct + if slf.is_diagnostic_namespace_variant { + if let Some(trait_def_id) = trait_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + tcx.local_def_id_to_hir_id(trait_def_id), + slf.span, + WrappedParserError { description: e.description, label: e.label }, + ); } + } else { + let reported = + struct_span_code_err!(tcx.dcx(), slf.span, E0231, "{}", e.description).emit(); + result = Err(reported); } } - - result } - pub fn format( - &self, - tcx: TyCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, - args: &FormatArgs<'tcx>, - ) -> String { - let trait_def_id = trait_ref.def_id; - let ctx = if self.is_diagnostic_namespace_variant { - Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } - } else { - Ctx::RustcOnUnimplemented { tcx, trait_def_id } - }; + result +} - // No point passing a snippet here, we already did that in `verify` - if let Ok(s) = FormatString::parse(self.symbol, None, self.span, &ctx) { - s.format(args) - } else { - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing the format string - // we don't want to show the potentially half assembled formatted string, - // therefore we fall back to just showing the input string in this case - // - // The actual parser errors are emitted earlier - // as lint warnings in OnUnimplementedFormatString::verify - self.symbol.as_str().into() - } +pub fn format_directive<'tcx>( + slf: &OnUnimplementedFormatString, + tcx: TyCtxt<'tcx>, + trait_ref: ty::TraitRef<'tcx>, + args: &FormatArgs<'tcx>, +) -> String { + let trait_def_id = trait_ref.def_id; + let ctx = if slf.is_diagnostic_namespace_variant { + Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } + } else { + Ctx::RustcOnUnimplemented { tcx, trait_def_id } + }; + + use crate::error_reporting::traits::on_unimplemented_format::{format, parse_format_string}; + + // No point passing a snippet here, we already did that in `verify` + if let Ok(s) = parse_format_string(slf.symbol, None, slf.span, &ctx) { + format(&s, args) + } else { + // we cannot return errors from processing the format string as hard error here + // as the diagnostic namespace guarantees that malformed input cannot cause an error + // + // if we encounter any error while processing the format string + // we don't want to show the potentially half assembled formatted string, + // therefore we fall back to just showing the input string in this case + // + // The actual parser errors are emitted earlier + // as lint warnings in OnUnimplementedFormatString::verify + slf.symbol.as_str().into() } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs index 171d05230d468..52643869d7800 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -1,322 +1,148 @@ use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit}; +use rustc_hir::attrs::diagnostic::*; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; -use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym}; +use rustc_span::{Ident, Symbol, kw, sym}; use crate::errors::InvalidOnClause; -/// Represents the `on` filter in `#[rustc_on_unimplemented]`. -#[derive(Debug)] -pub(crate) struct OnUnimplementedCondition { - span: Span, - pred: Predicate, -} - -impl OnUnimplementedCondition { - pub(crate) fn span(&self) -> Span { - self.span - } - - pub(crate) fn matches_predicate(&self, options: &ConditionOptions) -> bool { - self.pred.eval(&mut |p| match p { - FlagOrNv::Flag(b) => options.has_flag(*b), - FlagOrNv::NameValue(NameValue { name, value }) => { - let value = value.format(&options.generic_args); - options.contains(*name, value) - } - }) - } - - pub(crate) fn parse( - input: &MetaItemInner, - generics: &[Symbol], - ) -> Result { - let span = input.span(); - let pred = Predicate::parse(input, generics)?; - Ok(OnUnimplementedCondition { span, pred }) - } +pub fn matches_predicate(slf: &OnUnimplementedCondition, options: &ConditionOptions) -> bool { + slf.pred.eval(&mut |p| match p { + FlagOrNv::Flag(b) => options.has_flag(*b), + FlagOrNv::NameValue(NameValue { name, value }) => { + let value = format_filter(value, &options.generic_args); + options.contains(*name, value) + } + }) } -/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`]. -/// -/// It is similar to the predicate in the `cfg` attribute, -/// and may contain nested predicates. -#[derive(Debug)] -enum Predicate { - /// A condition like `on(crate_local)`. - Flag(Flag), - /// A match, like `on(Rhs = "Whatever")`. - Match(NameValue), - /// Negation, like `on(not($pred))`. - Not(Box), - /// True if all predicates are true, like `on(all($a, $b, $c))`. - All(Vec), - /// True if any predicate is true, like `on(any($a, $b, $c))`. - Any(Vec), +pub(crate) fn parse_condition( + input: &MetaItemInner, + generics: &[Symbol], +) -> Result { + let span = input.span(); + let pred = parse_predicate(input, generics)?; + Ok(OnUnimplementedCondition { span, pred }) } -impl Predicate { - fn parse(input: &MetaItemInner, generics: &[Symbol]) -> Result { - let meta_item = match input { - MetaItemInner::MetaItem(meta_item) => meta_item, - MetaItemInner::Lit(lit) => { - return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span }); - } - }; - - let Some(predicate) = meta_item.ident() else { - return Err(InvalidOnClause::ExpectedIdentifier { - span: meta_item.path.span, - path: meta_item.path.clone(), - }); - }; - - match meta_item.kind { - MetaItemKind::List(ref mis) => match predicate.name { - sym::any => Ok(Predicate::Any(Predicate::parse_sequence(mis, generics)?)), - sym::all => Ok(Predicate::All(Predicate::parse_sequence(mis, generics)?)), - sym::not => match &**mis { - [one] => Ok(Predicate::Not(Box::new(Predicate::parse(one, generics)?))), - [first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot { - span: first.span().to(last.span()), - }), - [] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }), - }, - invalid_pred => { - Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred }) - } +fn parse_predicate( + input: &MetaItemInner, + generics: &[Symbol], +) -> Result { + let meta_item = match input { + MetaItemInner::MetaItem(meta_item) => meta_item, + MetaItemInner::Lit(lit) => { + return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span }); + } + }; + + let Some(predicate) = meta_item.ident() else { + return Err(InvalidOnClause::ExpectedIdentifier { + span: meta_item.path.span, + path: meta_item.path.clone(), + }); + }; + + match meta_item.kind { + MetaItemKind::List(ref mis) => match predicate.name { + sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis, generics)?)), + sym::all => Ok(Predicate::All(parse_predicate_sequence(mis, generics)?)), + sym::not => match &**mis { + [one] => Ok(Predicate::Not(Box::new(parse_predicate(one, generics)?))), + [first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot { + span: first.span().to(last.span()), + }), + [] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }), }, - MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => { - let name = Name::parse(predicate, generics)?; - let value = FilterFormatString::parse(symbol); - let kv = NameValue { name, value }; - Ok(Predicate::Match(kv)) - } - MetaItemKind::Word => { - let flag = Flag::parse(predicate)?; - Ok(Predicate::Flag(flag)) + invalid_pred => { + Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred }) } + }, + MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => { + let name = parse_name(predicate, generics)?; + let value = parse_filter(symbol); + let kv = NameValue { name, value }; + Ok(Predicate::Match(kv)) } - } - - fn parse_sequence( - sequence: &[MetaItemInner], - generics: &[Symbol], - ) -> Result, InvalidOnClause> { - sequence.iter().map(|item| Predicate::parse(item, generics)).collect() - } - - fn eval(&self, eval: &mut impl FnMut(FlagOrNv<'_>) -> bool) -> bool { - match self { - Predicate::Flag(flag) => eval(FlagOrNv::Flag(flag)), - Predicate::Match(nv) => eval(FlagOrNv::NameValue(nv)), - Predicate::Not(not) => !not.eval(eval), - Predicate::All(preds) => preds.into_iter().all(|pred| pred.eval(eval)), - Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)), + MetaItemKind::Word => { + let flag = parse_flag(predicate)?; + Ok(Predicate::Flag(flag)) } } } -/// Represents a `MetaWord` in an `on`-filter. -#[derive(Debug, Clone, Copy)] -enum Flag { - /// Whether the code causing the trait bound to not be fulfilled - /// is part of the user's crate. - CrateLocal, - /// Whether the obligation is user-specified rather than derived. - Direct, - /// Whether we are in some kind of desugaring like - /// `?` or `try { .. }`. - FromDesugaring, +fn parse_predicate_sequence( + sequence: &[MetaItemInner], + generics: &[Symbol], +) -> Result, InvalidOnClause> { + sequence.iter().map(|item| parse_predicate(item, generics)).collect() } -impl Flag { - fn parse(Ident { name, span }: Ident) -> Result { - match name { - sym::crate_local => Ok(Flag::CrateLocal), - sym::direct => Ok(Flag::Direct), - sym::from_desugaring => Ok(Flag::FromDesugaring), - invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }), - } +fn parse_flag(Ident { name, span }: Ident) -> Result { + match name { + sym::crate_local => Ok(Flag::CrateLocal), + sym::direct => Ok(Flag::Direct), + sym::from_desugaring => Ok(Flag::FromDesugaring), + invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }), } } -/// A `MetaNameValueStr` in an `on`-filter. -/// -/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`. -#[derive(Debug, Clone)] -struct NameValue { - name: Name, - /// Something like `"&str"` or `"alloc::string::String"`, - /// in which case it just contains a single string piece. - /// But if it is something like `"&[{A}]"` then it must be formatted later. - value: FilterFormatString, -} - -/// The valid names of the `on` filter. -#[derive(Debug, Clone, Copy)] -enum Name { - Cause, - FromDesugaring, - SelfUpper, - GenericArg(Symbol), -} - -impl Name { - fn parse(Ident { name, span }: Ident, generics: &[Symbol]) -> Result { - match name { - kw::SelfUpper => Ok(Name::SelfUpper), - sym::from_desugaring => Ok(Name::FromDesugaring), - sym::cause => Ok(Name::Cause), - generic if generics.contains(&generic) => Ok(Name::GenericArg(generic)), - invalid_name => Err(InvalidOnClause::InvalidName { invalid_name, span }), - } +fn parse_name(Ident { name, span }: Ident, generics: &[Symbol]) -> Result { + match name { + kw::SelfUpper => Ok(Name::SelfUpper), + sym::from_desugaring => Ok(Name::FromDesugaring), + sym::cause => Ok(Name::Cause), + generic if generics.contains(&generic) => Ok(Name::GenericArg(generic)), + invalid_name => Err(InvalidOnClause::InvalidName { invalid_name, span }), } } -#[derive(Debug, Clone)] -enum FlagOrNv<'p> { - Flag(&'p Flag), - NameValue(&'p NameValue), -} +fn parse_filter(input: Symbol) -> FilterFormatString { + let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) + .map(|p| match p { + Piece::Lit(s) => LitOrArg::Lit(s.to_owned()), + // We just ignore formatspecs here + Piece::NextArgument(a) => match a.position { + // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even + // if the integer type has been resolved, to allow targeting all integers. + // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet, + // from the `Display` impl of `InferTy` to be precise. + // + // Don't try to format these later! + Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { + LitOrArg::Lit(format!("{{{arg}}}")) + } -/// Represents a value inside an `on` filter. -/// -/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`. -/// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`. -/// The `Arg` variant is used when it contains formatting like -/// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`. -#[derive(Debug, Clone)] -struct FilterFormatString { - pieces: Vec, -} + // FIXME(mejrs) We should check if these correspond to a generic of the trait. + Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()), -#[derive(Debug, Clone)] -enum LitOrArg { - Lit(String), - Arg(String), + // FIXME(mejrs) These should really be warnings/errors + Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")), + Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")), + }, + }) + .collect(); + FilterFormatString { pieces } } -impl FilterFormatString { - fn parse(input: Symbol) -> Self { - let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) - .map(|p| match p { - Piece::Lit(s) => LitOrArg::Lit(s.to_owned()), - // We just ignore formatspecs here - Piece::NextArgument(a) => match a.position { - // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even - // if the integer type has been resolved, to allow targeting all integers. - // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet, - // from the `Display` impl of `InferTy` to be precise. - // - // Don't try to format these later! - Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { - LitOrArg::Lit(format!("{{{arg}}}")) - } - - // FIXME(mejrs) We should check if these correspond to a generic of the trait. - Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()), - - // FIXME(mejrs) These should really be warnings/errors - Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")), - Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")), - }, - }) - .collect(); - Self { pieces } - } - - fn format(&self, generic_args: &[(Symbol, String)]) -> String { - let mut ret = String::new(); - - for piece in &self.pieces { - match piece { - LitOrArg::Lit(s) => ret.push_str(s), - LitOrArg::Arg(arg) => { - let s = Symbol::intern(arg); - match generic_args.iter().find(|(k, _)| *k == s) { - Some((_, val)) => ret.push_str(val), - None => { - // FIXME(mejrs) If we start checking as mentioned in - // FilterFormatString::parse then this shouldn't happen - let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); - } +fn format_filter(slf: &FilterFormatString, generic_args: &[(Symbol, String)]) -> String { + let mut ret = String::new(); + + for piece in &slf.pieces { + match piece { + LitOrArg::Lit(s) => ret.push_str(s), + LitOrArg::Arg(arg) => { + let s = Symbol::intern(arg); + match generic_args.iter().find(|(k, _)| *k == s) { + Some((_, val)) => ret.push_str(val), + None => { + // FIXME(mejrs) If we start checking as mentioned in + // FilterFormatString::parse then this shouldn't happen + let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); } } } } - - ret } -} -/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the -/// [`OnUnimplementedCondition`]. -/// -/// For example, given a -/// ```rust,ignore (just an example) -/// #[rustc_on_unimplemented( -/// on(all(from_desugaring = "QuestionMark"), -/// message = "the `?` operator can only be used in {ItemContext} \ -/// that returns `Result` or `Option` \ -/// (or another type that implements `{FromResidual}`)", -/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", -/// parent_label = "this function should return `Result` or `Option` to accept `?`" -/// ), -/// )] -/// pub trait FromResidual::Residual> { -/// ... -/// } -/// -/// async fn an_async_function() -> u32 { -/// let x: Option = None; -/// x?; //~ ERROR the `?` operator -/// 22 -/// } -/// ``` -/// it will look like this: -/// -/// ```rust,ignore (just an example) -/// ConditionOptions { -/// self_types: ["u32", "{integral}"], -/// from_desugaring: Some("QuestionMark"), -/// cause: None, -/// crate_local: false, -/// direct: true, -/// generic_args: [("Self","u32"), -/// ("R", "core::option::Option"), -/// ("R", "core::option::Option" ), -/// ], -/// } -/// ``` -#[derive(Debug)] -pub(crate) struct ConditionOptions { - /// All the self types that may apply. - pub(crate) self_types: Vec, - // The kind of compiler desugaring. - pub(crate) from_desugaring: Option, - /// Match on a variant of [rustc_infer::traits::ObligationCauseCode]. - pub(crate) cause: Option, - pub(crate) crate_local: bool, - /// Is the obligation "directly" user-specified, rather than derived? - pub(crate) direct: bool, - // A list of the generic arguments and their reified types. - pub(crate) generic_args: Vec<(Symbol, String)>, -} - -impl ConditionOptions { - fn has_flag(&self, name: Flag) -> bool { - match name { - Flag::CrateLocal => self.crate_local, - Flag::Direct => self.direct, - Flag::FromDesugaring => self.from_desugaring.is_some(), - } - } - fn contains(&self, name: Name, value: String) -> bool { - match name { - Name::SelfUpper => self.self_types.contains(&value), - Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)), - Name::Cause => self.cause == Some(value), - Name::GenericArg(arg) => self.generic_args.contains(&(arg, value)), - } - } + ret } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 360a01a8a3759..257f28121e40e 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -2,6 +2,7 @@ use std::fmt; use std::ops::Range; use errors::*; +use rustc_hir::attrs::diagnostic::*; use rustc_middle::ty::print::TraitRefPrintSugared; use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; use rustc_parse_format::{ @@ -11,42 +12,6 @@ use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_FORMAT_LITERALS; use rustc_span::def_id::DefId; use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; -/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", -/// either as string pieces or dynamic arguments. -#[derive(Debug)] -pub struct FormatString { - #[allow(dead_code, reason = "Debug impl")] - input: Symbol, - span: Span, - pieces: Vec, - /// The formatting string was parsed successfully but with warnings - pub warnings: Vec, -} - -#[derive(Debug)] -enum Piece { - Lit(String), - Arg(FormatArg), -} - -#[derive(Debug)] -enum FormatArg { - // A generic parameter, like `{T}` if we're on the `From` trait. - GenericParam { - generic_param: Symbol, - }, - // `{Self}` - SelfUpper, - /// `{This}` or `{TraitName}` - This, - /// The sugared form of the trait - Trait, - /// what we're in, like a function, method, closure etc. - ItemContext, - /// What the user typed, if it doesn't match anything we can use. - AsIs(String), -} - pub enum Ctx<'tcx> { // `#[rustc_on_unimplemented]` RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, @@ -54,58 +19,48 @@ pub enum Ctx<'tcx> { DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, } -#[derive(Debug)] -pub enum FormatWarning { - UnknownParam { argument_name: Symbol, span: Span }, - PositionalArgument { span: Span, help: String }, - InvalidSpecifier { name: String, span: Span }, - FutureIncompat { span: Span, help: String }, -} - -impl FormatWarning { - pub fn emit_warning<'tcx>(&self, tcx: TyCtxt<'tcx>, item_def_id: DefId) { - match *self { - FormatWarning::UnknownParam { argument_name, span } => { - let this = tcx.item_ident(item_def_id); - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - UnknownFormatParameterForOnUnimplementedAttr { - argument_name, - trait_name: this, - }, - ); - } +pub fn emit_warning<'tcx>(slf: &FormatWarning, tcx: TyCtxt<'tcx>, item_def_id: DefId) { + match *slf { + FormatWarning::UnknownParam { argument_name, span } => { + let this = tcx.item_ident(item_def_id); + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + tcx.local_def_id_to_hir_id(item_def_id), + span, + UnknownFormatParameterForOnUnimplementedAttr { + argument_name, + trait_name: this, + }, + ); } - FormatWarning::PositionalArgument { span, .. } => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - DisallowedPositionalArgument, - ); - } + } + FormatWarning::PositionalArgument { span, .. } => { + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + tcx.local_def_id_to_hir_id(item_def_id), + span, + DisallowedPositionalArgument, + ); } - FormatWarning::InvalidSpecifier { span, .. } => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - InvalidFormatSpecifier, - ); - } + } + FormatWarning::InvalidSpecifier { span, .. } => { + if let Some(item_def_id) = item_def_id.as_local() { + tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + tcx.local_def_id_to_hir_id(item_def_id), + span, + InvalidFormatSpecifier, + ); } - FormatWarning::FutureIncompat { .. } => { - // We've never deprecated anything in diagnostic namespace format strings - // but if we do we will emit a warning here + } + FormatWarning::FutureIncompat { .. } => { + // We've never deprecated anything in diagnostic namespace format strings + // but if we do we will emit a warning here - // FIXME(mejrs) in a couple releases, start emitting warnings for - // #[rustc_on_unimplemented] deprecated args - } + // FIXME(mejrs) in a couple releases, start emitting warnings for + // #[rustc_on_unimplemented] deprecated args } } } @@ -152,75 +107,69 @@ pub struct FormatArgs<'tcx> { pub generic_args: Vec<(Symbol, String)>, } -impl FormatString { - pub fn span(&self) -> Span { - self.span - } - - pub fn parse<'tcx>( - input: Symbol, - snippet: Option, - span: Span, - ctx: &Ctx<'tcx>, - ) -> Result { - let s = input.as_str(); - let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); - let pieces: Vec<_> = parser.by_ref().collect(); +pub fn parse_format_string<'tcx>( + input: Symbol, + snippet: Option, + span: Span, + ctx: &Ctx<'tcx>, +) -> Result { + let s = input.as_str(); + let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); + let pieces: Vec<_> = parser.by_ref().collect(); - if let Some(err) = parser.errors.into_iter().next() { - return Err(err); - } - let mut warnings = Vec::new(); + if let Some(err) = parser.errors.into_iter().next() { + return Err(err); + } + let mut warnings = Vec::new(); - let pieces = pieces - .into_iter() - .map(|piece| match piece { - RpfPiece::Lit(lit) => Piece::Lit(lit.into()), - RpfPiece::NextArgument(arg) => { - warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); - let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); - Piece::Arg(arg) - } - }) - .collect(); + let pieces = pieces + .into_iter() + .map(|piece| match piece { + RpfPiece::Lit(lit) => Piece::Lit(lit.into()), + RpfPiece::NextArgument(arg) => { + warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); + let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); + Piece::Arg(arg) + } + }) + .collect(); - Ok(FormatString { input, pieces, span, warnings }) - } + Ok(FormatString { input, pieces, span, warnings }) +} - pub fn format(&self, args: &FormatArgs<'_>) -> String { - let mut ret = String::new(); - for piece in &self.pieces { - match piece { - Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s), +pub fn format(slf: &FormatString, args: &FormatArgs<'_>) -> String { + let mut ret = String::new(); + for piece in &slf.pieces { + match piece { + Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s), - // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` - Piece::Arg(FormatArg::GenericParam { generic_param }) => { - // Should always be some but we can't raise errors here - let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) { - Some((_, val)) => val.to_string(), - None => generic_param.to_string(), - }; - ret.push_str(&value); - } - // `{Self}` - Piece::Arg(FormatArg::SelfUpper) => { - let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { - Some((_, val)) => val.to_string(), - None => "Self".to_string(), - }; - ret.push_str(&slf); - } + // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` + Piece::Arg(FormatArg::GenericParam { generic_param }) => { + // Should always be some but we can't raise errors here + let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) { + Some((_, val)) => val.to_string(), + None => generic_param.to_string(), + }; + ret.push_str(&value); + } + // `{Self}` + Piece::Arg(FormatArg::SelfUpper) => { + let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { + Some((_, val)) => val.to_string(), + None => "Self".to_string(), + }; + ret.push_str(&slf); + } - // It's only `rustc_onunimplemented` from here - Piece::Arg(FormatArg::This) => ret.push_str(&args.this), - Piece::Arg(FormatArg::Trait) => { - let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); - } - Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), + // It's only `rustc_onunimplemented` from here + Piece::Arg(FormatArg::This) => ret.push_str(&args.this), + Piece::Arg(FormatArg::Trait) => { + let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); } + Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), } - ret } + ret } fn parse_arg<'tcx>( From fa31d0b0deec69ee1984af16f201f3c0b4b136d5 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 2 Feb 2026 13:17:47 +0100 Subject: [PATCH 11/32] port over diagnostic::on_unimplemented --- Cargo.lock | 1 + compiler/rustc_attr_parsing/Cargo.toml | 1 + .../src/attributes/diagnostic/mod.rs | 117 ++++++++++ .../attributes/diagnostic/on_unimplemented.rs | 202 ++++++++++++++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + compiler/rustc_attr_parsing/src/context.rs | 2 + compiler/rustc_attr_parsing/src/lib.rs | 1 + .../src/error_codes/E0230.md | 28 +-- .../rustc_hir/src/attrs/data_structures.rs | 8 + compiler/rustc_hir/src/attrs/diagnostic.rs | 116 ++++++---- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_hir/src/lints.rs | 2 +- compiler/rustc_lint/src/early/diagnostics.rs | 23 +- compiler/rustc_lint/src/lints.rs | 44 ++++ compiler/rustc_lint_defs/src/lib.rs | 23 ++ compiler/rustc_passes/src/check_attr.rs | 56 ++++- compiler/rustc_span/src/symbol.rs | 1 + .../traits/fulfillment_errors.rs | 2 +- .../traits/on_unimplemented.rs | 133 ++++-------- .../traits/on_unimplemented_condition.rs | 20 +- .../traits/on_unimplemented_format.rs | 67 ++---- tests/ui/attributes/malformed-attrs.stderr | 34 +-- .../on_unimplemented/broken_format.rs | 7 - .../on_unimplemented/broken_format.stderr | 116 +++------- ...options_of_the_internal_rustc_attribute.rs | 15 -- ...ons_of_the_internal_rustc_attribute.stderr | 201 +++-------------- ...o_not_fail_parsing_on_invalid_options_1.rs | 5 - ...t_fail_parsing_on_invalid_options_1.stderr | 109 +++------- ...ed_options_and_continue_to_use_fallback.rs | 2 - ...ptions_and_continue_to_use_fallback.stderr | 33 ++- .../report_warning_on_duplicated_options.rs | 2 - ...eport_warning_on_duplicated_options.stderr | 36 +--- .../ui/on-unimplemented/bad-annotation.stderr | 12 +- tests/ui/unpretty/diagnostic-attr.stdout | 11 +- 34 files changed, 763 insertions(+), 669 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs diff --git a/Cargo.lock b/Cargo.lock index b00b9c55b7595..925d9991910f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3547,6 +3547,7 @@ dependencies = [ "rustc_lexer", "rustc_macros", "rustc_parse", + "rustc_parse_format", "rustc_session", "rustc_span", "rustc_target", diff --git a/compiler/rustc_attr_parsing/Cargo.toml b/compiler/rustc_attr_parsing/Cargo.toml index 0a11a2da0dcf8..886df58e8d6f0 100644 --- a/compiler/rustc_attr_parsing/Cargo.toml +++ b/compiler/rustc_attr_parsing/Cargo.toml @@ -15,6 +15,7 @@ rustc_hir = { path = "../rustc_hir" } rustc_lexer = { path = "../rustc_lexer" } rustc_macros = { path = "../rustc_macros" } rustc_parse = { path = "../rustc_parse" } +rustc_parse_format = { path = "../rustc_parse_format" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_target = { path = "../rustc_target" } diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs new file mode 100644 index 0000000000000..280691f308eec --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -0,0 +1,117 @@ +#![allow(warnings)] + +use std::ops::Range; + +use rustc_hir::attrs::diagnostic::{FormatArg, FormatString, Piece}; +use rustc_hir::lints::FormatWarning; +use rustc_parse_format::{ + Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, +}; +use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; + +pub mod on_unimplemented; + +#[derive(Copy, Clone)] +pub(crate) enum Ctx { + // `#[rustc_on_unimplemented]` + RustcOnUnimplemented, + // `#[diagnostic::...]` + DiagnosticOnUnimplemented, +} + +pub(crate) fn parse_format_string( + input: Symbol, + snippet: Option, + span: Span, + ctx: Ctx, +) -> Result<(FormatString, Vec), ParseError> { + let s = input.as_str(); + let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); + let pieces: Vec<_> = parser.by_ref().collect(); + + if let Some(err) = parser.errors.into_iter().next() { + return Err(err); + } + let mut warnings = Vec::new(); + + let pieces = pieces + .into_iter() + .map(|piece| match piece { + RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)), + RpfPiece::NextArgument(arg) => { + warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); + let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); + Piece::Arg(arg) + } + }) + .collect(); + + Ok((FormatString { input, pieces, span }, warnings)) +} + +fn parse_arg( + arg: &Argument<'_>, + ctx: Ctx, + warnings: &mut Vec, + input_span: Span, + is_source_literal: bool, +) -> FormatArg { + let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); + + match arg.position { + // Something like "hello {name}" + Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) { + // Only `#[rustc_on_unimplemented]` can use these + (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, + (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, + (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, + // Any attribute can use these + ( + Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + kw::SelfUpper, + ) => FormatArg::SelfUpper, + ( + Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + generic_param, + ) => FormatArg::GenericParam { generic_param, span }, + }, + + // `{:1}` and `{}` are ignored + Position::ArgumentIs(idx) => { + warnings.push(FormatWarning::PositionalArgument { + span, + help: format!("use `{{{idx}}}` to print a number in braces"), + }); + FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}"))) + } + Position::ArgumentImplicitlyIs(_) => { + warnings.push(FormatWarning::PositionalArgument { + span, + help: String::from("use `{{}}` to print empty braces"), + }); + FormatArg::AsIs(sym::empty_braces) + } + } +} + +/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything +/// with specifiers, so emit a warning if they are used. +fn warn_on_format_spec( + spec: &FormatSpec<'_>, + warnings: &mut Vec, + input_span: Span, + is_source_literal: bool, +) { + if spec.ty != "" { + let span = spec + .ty_span + .as_ref() + .map(|inner| slice_span(input_span, inner.clone(), is_source_literal)) + .unwrap_or(input_span); + warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() }) + } +} + +fn slice_span(input: Span, Range { start, end }: Range, is_source_literal: bool) -> Span { + if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs new file mode 100644 index 0000000000000..477cc5e14eb08 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -0,0 +1,202 @@ +#![allow(warnings)] +use rustc_hir::attrs::diagnostic::OnUnimplementedDirective; +use rustc_hir::lints::AttributeLintKind; +use rustc_session::lint::builtin::{ + MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, +}; +use thin_vec::thin_vec; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; +use crate::attributes::template; +/// Folds all uses of `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`. +/// FIXME(mejrs): add an example +#[derive(Default)] +pub struct OnUnimplementedParser { + directive: Option<(Span, OnUnimplementedDirective)>, +} + +impl AttributeParser for OnUnimplementedParser { + const ATTRIBUTES: AcceptMapping = &[ + (&[sym::diagnostic, sym::on_unimplemented], template!(Word), |this, cx, args| { + let span = cx.attr_span; + + let items = match args { + ArgParser::List(items) => items, + ArgParser::NoArgs => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnUnimplemented, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + return; + } + }; + + let Some(directive) = parse_directive_items(cx, Ctx::DiagnosticOnUnimplemented, items) + else { + return; + }; + merge_directives(cx, &mut this.directive, (span, directive)); + }), + // todo (&[sym::rustc_on_unimplemented], template!(Word), |this, cx, args| {}), + ]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option { + self.directive.map(|(span, directive)| AttributeKind::OnUnimplemented { + span, + directive: Some(Box::new(directive)), + }) + } +} + +fn merge_directives( + cx: &mut AcceptContext<'_, '_, S>, + first: &mut Option<(Span, OnUnimplementedDirective)>, + later: (Span, OnUnimplementedDirective), +) { + if let Some((first_span, first)) = first { + merge(cx, &mut first.message, later.1.message, "message"); + merge(cx, &mut first.label, later.1.label, "label"); + first.notes.extend(later.1.notes); + } else { + *first = Some(later); + } +} + +fn merge( + cx: &mut AcceptContext<'_, '_, S>, + first: &mut Option<(Span, T)>, + later: Option<(Span, T)>, + option_name: &'static str, +) { + match (first, later) { + (Some(_) | None, None) => {} + (Some((first_span, _)), Some((later_span, _))) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::IgnoredDiagnosticOption { + first_span: *first_span, + later_span, + option_name, + }, + later_span, + ); + } + (first @ None, Some(later)) => { + first.get_or_insert(later); + } + } +} + +fn parse_directive_items( + cx: &mut AcceptContext, + ctx: Ctx, + items: &MetaItemListParser, +) -> Option { + let condition = None; + let mut message = None; + let mut label = None; + let mut notes = ThinVec::new(); + let mut parent_label = None; + let mut subcommands = ThinVec::new(); + let mut append_const_msg = None; + + for item in items.mixed() { + // At this point, we are expecting any of: + // message = "..", label = "..", note = ".." + let Some((name, value, value_span)) = (try { + let item = item.meta_item()?; + let name = item.ident()?.name; + let nv = item.args().name_value()?; + let value = nv.value_as_str()?; + (name, value, nv.value_span) + }) else { + let span = item.span(); + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + continue; + }; + + let mut parse = |input| { + let snippet = cx.sess.source_map().span_to_snippet(value_span).ok(); + let is_snippet = snippet.is_some(); + match parse_format_string(input, snippet, value_span, ctx) { + Ok((f, warnings)) => { + for warning in warnings { + let (FormatWarning::InvalidSpecifier { span, .. } + | FormatWarning::PositionalArgument { span, .. }) = warning; + cx.emit_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + AttributeLintKind::MalformedDiagnosticFormat { warning }, + span, + ); + } + + f + } + Err(e) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + AttributeLintKind::DiagnosticWrappedParserError { + description: e.description, + label: e.label, + span: slice_span(value_span, e.span, is_snippet), + }, + value_span, + ); + // We could not parse the input, just use it as-is. + FormatString { input, span: value_span, pieces: thin_vec![Piece::Lit(input)] } + } + } + }; + match name { + sym::message => { + if message.is_none() { + message.insert((item.span(), parse(value))); + } else { + // warn + } + } + sym::label => { + if label.is_none() { + label.insert((item.span(), parse(value))); + } else { + // warn + } + } + sym::note => notes.push(parse(value)), + _other => { + let span = item.span(); + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + continue; + } + } + } + + Some(OnUnimplementedDirective { + condition, + subcommands, + message, + label, + notes, + parent_label, + append_const_msg, + }) +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 0d328d5cc6a70..9047ac8a86108 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -39,6 +39,7 @@ pub(crate) mod confusables; pub(crate) mod crate_level; pub(crate) mod debugger; pub(crate) mod deprecation; +pub(crate) mod diagnostic; pub(crate) mod do_not_recommend; pub(crate) mod doc; pub(crate) mod dummy; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index b4e91ecebeeb7..6116e40cf01e0 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -26,6 +26,7 @@ use crate::attributes::confusables::*; use crate::attributes::crate_level::*; use crate::attributes::debugger::*; use crate::attributes::deprecation::*; +use crate::attributes::diagnostic::on_unimplemented::*; use crate::attributes::do_not_recommend::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; @@ -147,6 +148,7 @@ attribute_parsers!( DocParser, MacroUseParser, NakedParser, + OnUnimplementedParser, RustcCguTestAttributeParser, StabilityParser, UsedParser, diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 76afcb2a260ad..b70eb93d443a7 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -80,6 +80,7 @@ #![cfg_attr(bootstrap, feature(if_let_guard))] #![feature(decl_macro)] #![feature(iter_intersperse)] +#![feature(try_blocks)] #![recursion_limit = "256"] // tidy-alphabetical-end diff --git a/compiler/rustc_error_codes/src/error_codes/E0230.md b/compiler/rustc_error_codes/src/error_codes/E0230.md index c30a7e38e9c48..b6dcf4b977741 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0230.md +++ b/compiler/rustc_error_codes/src/error_codes/E0230.md @@ -1,26 +1,4 @@ -The `#[rustc_on_unimplemented]` attribute lets you specify a custom error -message for when a particular trait isn't implemented on a type placed in a -position that needs that trait. For example, when the following code is -compiled: +#### Note: this error code is no longer emitted by the compiler. -```compile_fail,E0230 -#![feature(rustc_attrs)] -#![allow(internal_features)] - -#[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{B}>`"] // error -trait BadAnnotation {} -``` - -There will be an error about `bool` not implementing `Index`, followed by a -note saying "the type `bool` cannot be indexed by `u8`". - -As you can see, you can specify type parameters in curly braces for -instantiation with the actual types (using the regular format string syntax) in -a given situation. Furthermore, `{Self}` will be instantiated to the type (in -this case, `bool`) that we tried to use. - -This error appears when the curly braces contain an identifier which doesn't -match with any of the type parameters or the string `Self`. This might happen -if you misspelled a type parameter, or if you intended to use literal curly -braces. If it is the latter, escape the curly braces with a second curly brace -of the same type; e.g., a literal `{` is `{{`. +The `#[rustc_on_unimplemented]` attribute used to raise this error for various +misuses of the attribute; these are now warnings. diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 5d154cef66a65..84121edf04437 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -16,6 +16,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; pub use rustc_target::spec::SanitizerSet; use thin_vec::ThinVec; +use crate::attrs::diagnostic::*; use crate::attrs::pretty_printing::PrintAttribute; use crate::limit::Limit; use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability}; @@ -1079,6 +1080,13 @@ pub enum AttributeKind { /// Represents `#[non_exhaustive]` NonExhaustive(Span), + /// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`. + OnUnimplemented { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[optimize(size|speed)]` Optimize(OptimizeAttr, Span), diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 0298b7684caf7..95b10865b34c6 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -1,29 +1,41 @@ //! Contains the data structures used by the diagnostic attribute family. - +pub use rustc_ast::attr::data_structures::*; +use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; use rustc_span::{DesugaringKind, Span, Symbol}; +use thin_vec::ThinVec; -/// Represents a format string in a on_unimplemented attribute, -/// like the "content" in `#[diagnostic::on_unimplemented(message = "content")]` -#[derive(Clone, Debug)] -pub struct OnUnimplementedFormatString { - /// Symbol of the format string, i.e. `"content"` - pub symbol: Symbol, - /// The span of the format string, i.e. `"content"` - pub span: Span, - pub is_diagnostic_namespace_variant: bool, -} +use crate::attrs::PrintAttribute; -#[derive(Debug)] +#[derive(Clone, Default, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct OnUnimplementedDirective { pub condition: Option, - pub subcommands: Vec, - pub message: Option<(Span, OnUnimplementedFormatString)>, - pub label: Option<(Span, OnUnimplementedFormatString)>, - pub notes: Vec, - pub parent_label: Option, + pub subcommands: ThinVec, + pub message: Option<(Span, FormatString)>, + pub label: Option<(Span, FormatString)>, + pub notes: ThinVec, + pub parent_label: Option, pub append_const_msg: Option, } +impl OnUnimplementedDirective { + pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { + if let Some((_, message)) = &self.message { + message.visit_params(visit); + } + if let Some((_, label)) = &self.label { + label.visit_params(visit); + } + + if let Some(parent_label) = &self.parent_label { + parent_label.visit_params(visit); + } + + for note in &self.notes { + note.visit_params(visit); + } + } +} + /// For the `#[rustc_on_unimplemented]` attribute #[derive(Default, Debug)] pub struct OnUnimplementedNote { @@ -36,7 +48,18 @@ pub struct OnUnimplementedNote { } /// Append a message for `[const] Trait` errors. -#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Debug, + Default, + HashStable_Generic, + Encodable, + Decodable, + PrintAttribute +)] pub enum AppendConstMessage { #[default] Default, @@ -45,26 +68,35 @@ pub enum AppendConstMessage { /// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces", /// either as string pieces or dynamic arguments. -#[derive(Debug)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct FormatString { pub input: Symbol, pub span: Span, - pub pieces: Vec, - /// The formatting string was parsed successfully but with warnings - pub warnings: Vec, + pub pieces: ThinVec, } -#[derive(Debug)] +impl FormatString { + fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { + for piece in &self.pieces { + if let Piece::Arg(FormatArg::GenericParam { generic_param, span }) = piece { + visit(*generic_param, *span); + } + } + } +} + +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Piece { - Lit(String), + Lit(Symbol), Arg(FormatArg), } -#[derive(Debug)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum FormatArg { // A generic parameter, like `{T}` if we're on the `From` trait. GenericParam { generic_param: Symbol, + span: Span, }, // `{Self}` SelfUpper, @@ -75,19 +107,11 @@ pub enum FormatArg { /// what we're in, like a function, method, closure etc. ItemContext, /// What the user typed, if it doesn't match anything we can use. - AsIs(String), -} - -#[derive(Debug)] -pub enum FormatWarning { - UnknownParam { argument_name: Symbol, span: Span }, - PositionalArgument { span: Span, help: String }, - InvalidSpecifier { name: String, span: Span }, - FutureIncompat { span: Span, help: String }, + AsIs(Symbol), } /// Represents the `on` filter in `#[rustc_on_unimplemented]`. -#[derive(Debug)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct OnUnimplementedCondition { pub span: Span, pub pred: Predicate, @@ -97,7 +121,7 @@ pub struct OnUnimplementedCondition { /// /// It is similar to the predicate in the `cfg` attribute, /// and may contain nested predicates. -#[derive(Debug)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Predicate { /// A condition like `on(crate_local)`. Flag(Flag), @@ -106,9 +130,9 @@ pub enum Predicate { /// Negation, like `on(not($pred))`. Not(Box), /// True if all predicates are true, like `on(all($a, $b, $c))`. - All(Vec), + All(ThinVec), /// True if any predicate is true, like `on(any($a, $b, $c))`. - Any(Vec), + Any(ThinVec), } impl Predicate { @@ -124,7 +148,7 @@ impl Predicate { } /// Represents a `MetaWord` in an `on`-filter. -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Flag { /// Whether the code causing the trait bound to not be fulfilled /// is part of the user's crate. @@ -139,7 +163,7 @@ pub enum Flag { /// A `MetaNameValueStr` in an `on`-filter. /// /// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`. -#[derive(Debug, Clone)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct NameValue { pub name: Name, /// Something like `"&str"` or `"alloc::string::String"`, @@ -149,7 +173,7 @@ pub struct NameValue { } /// The valid names of the `on` filter. -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Name { Cause, FromDesugaring, @@ -169,15 +193,15 @@ pub enum FlagOrNv<'p> { /// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`. /// The `Arg` variant is used when it contains formatting like /// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`. -#[derive(Debug, Clone)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct FilterFormatString { - pub pieces: Vec, + pub pieces: ThinVec, } -#[derive(Debug, Clone)] +#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum LitOrArg { - Lit(String), - Arg(String), + Lit(Symbol), + Arg(Symbol), } /// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 0b20ea4d6a83a..945356804ae49 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -77,6 +77,7 @@ impl AttributeKind { NoMangle(..) => Yes, // Needed for rustdoc NoStd(..) => No, NonExhaustive(..) => Yes, // Needed for rustdoc + OnUnimplemented { .. } => Yes, Optimize(..) => No, PanicRuntime => No, PatchableFunctionEntry { .. } => Yes, diff --git a/compiler/rustc_hir/src/lints.rs b/compiler/rustc_hir/src/lints.rs index eba2d182d2c48..1589a6de220e7 100644 --- a/compiler/rustc_hir/src/lints.rs +++ b/compiler/rustc_hir/src/lints.rs @@ -1,6 +1,6 @@ use rustc_data_structures::fingerprint::Fingerprint; -pub use rustc_lint_defs::AttributeLintKind; use rustc_lint_defs::LintId; +pub use rustc_lint_defs::{AttributeLintKind, FormatWarning}; use rustc_macros::HashStable_Generic; use rustc_span::Span; diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 3da2b1bf4069e..48a4f1136f65d 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -4,7 +4,7 @@ use rustc_ast::util::unicode::TEXT_FLOW_CONTROL_CHARS; use rustc_errors::{ Applicability, Diag, DiagArgValue, LintDiagnostic, elided_lifetime_in_path_suggestion, }; -use rustc_hir::lints::AttributeLintKind; +use rustc_hir::lints::{AttributeLintKind, FormatWarning}; use rustc_middle::middle::stability; use rustc_middle::ty::TyCtxt; use rustc_session::Session; @@ -446,5 +446,26 @@ pub fn decorate_attribute_lint( &AttributeLintKind::ExpectedNoArgs => lints::ExpectedNoArgs.decorate_lint(diag), &AttributeLintKind::ExpectedNameValue => lints::ExpectedNameValue.decorate_lint(diag), + &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { + lints::MalformedOnUnimplementedAttrLint { span }.decorate_lint(diag) + } + AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning { + FormatWarning::PositionalArgument { .. } => { + lints::DisallowedPositionalArgument.decorate_lint(diag) + } + FormatWarning::InvalidSpecifier { .. } => { + lints::InvalidFormatSpecifier.decorate_lint(diag) + } + }, + AttributeLintKind::DiagnosticWrappedParserError { description, label, span } => { + lints::WrappedParserError { description, label, span: *span }.decorate_lint(diag) + } + &AttributeLintKind::IgnoredDiagnosticOption { option_name, first_span, later_span } => { + lints::IgnoredDiagnosticOption { option_name, first_span, later_span } + .decorate_lint(diag) + } + &AttributeLintKind::MissingOptionsForOnUnimplemented => { + lints::MissingOptionsForOnUnimplementedAttr.decorate_lint(diag) + } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 5627f34f82e97..6bcbecce91493 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3869,3 +3869,47 @@ pub(crate) struct UnreachableCfgSelectPredicateWildcard { #[label("always matches")] pub wildcard_span: Span, } + +#[derive(LintDiagnostic)] +#[diag("positional format arguments are not allowed here")] +#[help( + "only named format arguments with the name of one of the generic types are allowed in this context" +)] +pub(crate) struct DisallowedPositionalArgument; + +#[derive(LintDiagnostic)] +#[diag("invalid format specifier")] +#[help("no format specifier are supported in this position")] +pub(crate) struct InvalidFormatSpecifier; + +#[derive(LintDiagnostic)] +#[diag("{$description}")] +pub(crate) struct WrappedParserError<'a> { + pub description: &'a str, + #[label("{$label}")] + pub span: Span, + pub label: &'a str, +} + +#[derive(LintDiagnostic)] +#[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")] +pub(crate) struct IgnoredDiagnosticOption { + pub option_name: &'static str, + #[label("`{$option_name}` is first declared here")] + pub first_span: Span, + #[label("`{$option_name}` is later redundantly declared here")] + pub later_span: Span, +} + +#[derive(LintDiagnostic)] +#[diag("missing options for `on_unimplemented` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnUnimplementedAttr; + +#[derive(LintDiagnostic)] +#[diag("malformed `on_unimplemented` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnUnimplementedAttrLint { + #[label("invalid option found here")] + pub span: Span, +} diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index a1b8b135819a0..b2664b4794b48 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -834,6 +834,29 @@ pub enum AttributeLintKind { MalformedDoc, ExpectedNoArgs, ExpectedNameValue, + MalformedOnUnimplementedAttr { + span: Span, + }, + MalformedDiagnosticFormat { + warning: FormatWarning, + }, + DiagnosticWrappedParserError { + description: String, + label: String, + span: Span, + }, + IgnoredDiagnosticOption { + option_name: &'static str, + first_span: Span, + later_span: Span, + }, + MissingOptionsForOnUnimplemented, +} + +#[derive(Debug, Clone, HashStable_Generic)] +pub enum FormatWarning { + PositionalArgument { span: Span, help: String }, + InvalidSpecifier { name: String, span: Span }, } pub type RegisteredTools = FxIndexSet; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 9af9398d78b96..34a2a519f4c4c 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -20,6 +20,7 @@ use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, }; +use rustc_hir::attrs::diagnostic::OnUnimplementedDirective; use rustc_hir::attrs::{ AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet, @@ -28,9 +29,9 @@ use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; use rustc_hir::intravisit::{self, Visitor}; use rustc_hir::{ - self as hir, Attribute, CRATE_HIR_ID, Constness, FnSig, ForeignItem, HirId, Item, ItemKind, - MethodKind, PartialConstStability, Safety, Stability, StabilityLevel, Target, TraitItem, - find_attr, + self as hir, Attribute, CRATE_HIR_ID, Constness, FnSig, ForeignItem, GenericParamKind, HirId, + Item, ItemKind, MethodKind, Node, ParamName, PartialConstStability, Safety, Stability, + StabilityLevel, Target, TraitItem, find_attr, }; use rustc_macros::LintDiagnostic; use rustc_middle::hir::nested_filter; @@ -43,13 +44,14 @@ use rustc_middle::{bug, span_bug}; use rustc_session::config::CrateType; use rustc_session::lint; use rustc_session::lint::builtin::{ - CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MISPLACED_DIAGNOSTIC_ATTRIBUTES, - UNUSED_ATTRIBUTES, + CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES, }; use rustc_session::parse::feature_err; use rustc_span::edition::Edition; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::error_reporting::traits::on_unimplemented_format::errors::UnknownFormatParameterForOnUnimplementedAttr; use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs}; use rustc_trait_selection::traits::ObligationCtxt; use tracing::debug; @@ -231,6 +233,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { self.check_rustc_must_implement_one_of(*attr_span, fn_names, hir_id,target) }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, + Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -384,9 +387,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Unparsed(attr_item) => { style = Some(attr_item.style); match attr.path().as_slice() { - [sym::diagnostic, sym::on_unimplemented, ..] => { - self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target) - } [sym::diagnostic, sym::on_const, ..] => { self.check_diagnostic_on_const(attr.span(), hir_id, target, item) } @@ -607,7 +607,13 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } /// Checks if `#[diagnostic::on_unimplemented]` is applied to a trait definition - fn check_diagnostic_on_unimplemented(&self, attr_span: Span, hir_id: HirId, target: Target) { + fn check_diagnostic_on_unimplemented( + &self, + attr_span: Span, + hir_id: HirId, + target: Target, + directive: Option<&OnUnimplementedDirective>, + ) { if !matches!(target, Target::Trait) { self.tcx.emit_node_span_lint( MISPLACED_DIAGNOSTIC_ATTRIBUTES, @@ -616,6 +622,38 @@ impl<'tcx> CheckAttrVisitor<'tcx> { DiagnosticOnUnimplementedOnlyForTraits, ); } + + if let Some(directive) = directive { + if let Node::Item(Item { + kind: ItemKind::Trait(_, _, _, trait_name, generics, _, _), + .. + }) = self.tcx.hir_node(hir_id) + { + directive.visit_params(&mut |argument_name, span| { + let has_generic = generics.params.iter().any(|p| { + if !matches!(p.kind, GenericParamKind::Lifetime { .. }) + && let ParamName::Plain(name) = p.name + && name.name == argument_name + { + true + } else { + false + } + }); + if !has_generic { + self.tcx.emit_node_span_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + hir_id, + span, + UnknownFormatParameterForOnUnimplementedAttr { + argument_name, + trait_name: *trait_name, + }, + ) + } + }) + } + } } /// Checks if `#[diagnostic::on_const]` is applied to a trait impl diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9e16ee115212e..ef2044d2d070e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -864,6 +864,7 @@ symbols! { // it's clearer that it's intended as a dummy value, and more likely // to be detected if it accidentally does get used. empty: "", + empty_braces: "{}", emscripten_wasm_eh, enable, end, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 3e367320a0bfb..118c7ed9380f4 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -392,7 +392,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { if let Some(s) = label { // If it has a custom `#[rustc_on_unimplemented]` // error message, let's display it as the label! - err.span_label(span, s); + err.span_label(span, s.as_str().to_owned()); if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) // When the self type is a type param We don't need to "the trait // `std::marker::Sized` is not implemented for `T`" as we will point diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index b1fdfa1b42632..945a189ab6227 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -5,13 +5,15 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_errors::codes::*; use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_hir as hir; +use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{ - AppendConstMessage, ConditionOptions, FormatString, FormatWarning, OnUnimplementedDirective, - OnUnimplementedFormatString, OnUnimplementedNote, + AppendConstMessage, ConditionOptions, FormatString, OnUnimplementedDirective, + OnUnimplementedNote, Piece, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::{AttrArgs, Attribute}; +pub use rustc_hir::lints::FormatWarning; +use rustc_hir::{AttrArgs, Attribute, find_attr}; use rustc_macros::LintDiagnostic; use rustc_middle::bug; use rustc_middle::ty::print::PrintTraitRefExt; @@ -20,6 +22,7 @@ use rustc_session::lint::builtin::{ MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, }; use rustc_span::{Span, Symbol, sym}; +use thin_vec::{ThinVec, thin_vec}; use tracing::{debug, info}; use super::{ObligationCauseCode, PredicateObligation}; @@ -341,16 +344,16 @@ pub struct MissingOptionsForOnUnimplementedAttr; #[derive(LintDiagnostic)] #[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")] -pub struct IgnoredDiagnosticOption { +pub(crate) struct IgnoredDiagnosticOption { pub option_name: &'static str, - #[label("`{$option_name}` is already declared here")] - pub span: Span, #[label("`{$option_name}` is first declared here")] - pub prev_span: Span, + pub first_span: Span, + #[label("`{$option_name}` is later redundantly declared here")] + pub later_span: Span, } impl IgnoredDiagnosticOption { - pub fn maybe_emit_warning<'tcx>( + fn maybe_emit_warning<'tcx>( tcx: TyCtxt<'tcx>, item_def_id: DefId, new: Option, @@ -364,7 +367,7 @@ impl IgnoredDiagnosticOption { MALFORMED_DIAGNOSTIC_ATTRIBUTES, tcx.local_def_id_to_hir_id(item_def_id), new_item, - IgnoredDiagnosticOption { span: new_item, prev_span: old_item, option_name }, + IgnoredDiagnosticOption { later_span: new_item, first_span: old_item, option_name }, ); } } @@ -419,9 +422,9 @@ fn parse_directive<'tcx>( let mut message = None; let mut label = None; - let mut notes = Vec::new(); + let mut notes = ThinVec::new(); let mut parent_label = None; - let mut subcommands = vec![]; + let mut subcommands = ThinVec::new(); let mut append_const_msg = None; let get_value_and_span = |item: &_, key| { @@ -550,6 +553,8 @@ pub fn of_item_directive<'tcx>( tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) } { return parse_attribute_directive(attr, false, tcx, item_def_id); + } else if attr == sym::on_unimplemented { + Ok(find_attr!(tcx.get_all_attrs(item_def_id), AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref().cloned()).flatten()) } else { tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) .filter_map(|attr| parse_attribute_directive(attr, true, tcx, item_def_id).transpose()) @@ -636,7 +641,7 @@ fn parse_attribute_directive<'tcx>( Ok(Some(OnUnimplementedDirective { condition: None, message: None, - subcommands: vec![], + subcommands: thin_vec![], label: Some(( attr.span(), try_parse_format_string( @@ -647,7 +652,7 @@ fn parse_attribute_directive<'tcx>( is_diagnostic_namespace_variant, )?, )), - notes: Vec::new(), + notes: thin_vec![], parent_label: None, append_const_msg: None, })) @@ -747,37 +752,22 @@ pub(crate) fn evaluate_directive<'tcx>( } OnUnimplementedNote { - label: label.map(|l| format_directive(&l.1, tcx, trait_ref, args)), - message: message.map(|m| format_directive(&m.1, tcx, trait_ref, args)), - notes: notes.into_iter().map(|n| format_directive(&n, tcx, trait_ref, args)).collect(), - parent_label: parent_label.map(|e_s| format_directive(&e_s, tcx, trait_ref, args)), + label: label.map(|l| format_directive(l.1, tcx, trait_ref, args)), + message: message.map(|m| format_directive(m.1, tcx, trait_ref, args)), + notes: notes.into_iter().map(|n| format_directive(n, tcx, trait_ref, args)).collect(), + parent_label: parent_label.map(|e_s| format_directive(e_s, tcx, trait_ref, args)), append_const_msg, } } fn try_parse_format_string<'tcx>( tcx: TyCtxt<'tcx>, - item_def_id: DefId, - from: Symbol, + trait_def_id: DefId, + input: Symbol, span: Span, is_diagnostic_namespace_variant: bool, -) -> Result { - let result = - OnUnimplementedFormatString { symbol: from, span, is_diagnostic_namespace_variant }; - verify(&result, tcx, item_def_id)?; - Ok(result) -} - -fn verify<'tcx>( - slf: &OnUnimplementedFormatString, - tcx: TyCtxt<'tcx>, - trait_def_id: DefId, -) -> Result<(), ErrorGuaranteed> { - if !tcx.is_trait(trait_def_id) { - return Ok(()); - }; - - let ctx = if slf.is_diagnostic_namespace_variant { +) -> Result { + let ctx = if is_diagnostic_namespace_variant { Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } } else { Ctx::RustcOnUnimplemented { tcx, trait_def_id } @@ -787,12 +777,12 @@ fn verify<'tcx>( use crate::error_reporting::traits::on_unimplemented_format::parse_format_string; - let snippet = tcx.sess.source_map().span_to_snippet(slf.span).ok(); - match parse_format_string(slf.symbol, snippet, slf.span, &ctx) { + let snippet = tcx.sess.source_map().span_to_snippet(span).ok(); + let ret = match parse_format_string(input, snippet, span, &ctx) { // Warnings about format specifiers, deprecated parameters, wrong parameters etc. // In other words we'd like to let the author know, but we can still try to format the string later - Ok(FormatString { warnings, .. }) => { - if slf.is_diagnostic_namespace_variant { + Ok((f, warnings)) => { + if is_diagnostic_namespace_variant { for w in warnings { use crate::error_reporting::traits::on_unimplemented_format::emit_warning; @@ -801,17 +791,6 @@ fn verify<'tcx>( } else { for w in warnings { match w { - FormatWarning::UnknownParam { argument_name, span } => { - let reported = struct_span_code_err!( - tcx.dcx(), - span, - E0230, - "cannot find parameter {} on this trait", - argument_name - ) - .emit(); - result = Err(reported); - } FormatWarning::PositionalArgument { span, .. } => { let reported = struct_span_code_err!( tcx.dcx(), @@ -822,11 +801,11 @@ fn verify<'tcx>( .emit(); result = Err(reported); } - FormatWarning::InvalidSpecifier { .. } - | FormatWarning::FutureIncompat { .. } => {} + FormatWarning::InvalidSpecifier { .. } => {} } } - } + }; + f } // Error from the underlying `rustc_parse_format::Parser` Err(e) => { @@ -835,54 +814,36 @@ fn verify<'tcx>( // // if we encounter any error while processing we nevertheless want to show it as warning // so that users are aware that something is not correct - if slf.is_diagnostic_namespace_variant { + if is_diagnostic_namespace_variant { if let Some(trait_def_id) = trait_def_id.as_local() { tcx.emit_node_span_lint( MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, tcx.local_def_id_to_hir_id(trait_def_id), - slf.span, + span, WrappedParserError { description: e.description, label: e.label }, ); } } else { let reported = - struct_span_code_err!(tcx.dcx(), slf.span, E0231, "{}", e.description).emit(); + struct_span_code_err!(tcx.dcx(), span, E0231, "{}", e.description).emit(); result = Err(reported); } + + // We could not parse the input, just use it as-is. + FormatString { input, span, pieces: thin_vec![Piece::Lit(input)] } } - } + }; - result + result?; + Ok(ret) } pub fn format_directive<'tcx>( - slf: &OnUnimplementedFormatString, - tcx: TyCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, + slf: FormatString, + _tcx: TyCtxt<'tcx>, + _trait_ref: ty::TraitRef<'tcx>, args: &FormatArgs<'tcx>, ) -> String { - let trait_def_id = trait_ref.def_id; - let ctx = if slf.is_diagnostic_namespace_variant { - Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } - } else { - Ctx::RustcOnUnimplemented { tcx, trait_def_id } - }; - - use crate::error_reporting::traits::on_unimplemented_format::{format, parse_format_string}; - - // No point passing a snippet here, we already did that in `verify` - if let Ok(s) = parse_format_string(slf.symbol, None, slf.span, &ctx) { - format(&s, args) - } else { - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing the format string - // we don't want to show the potentially half assembled formatted string, - // therefore we fall back to just showing the input string in this case - // - // The actual parser errors are emitted earlier - // as lint warnings in OnUnimplementedFormatString::verify - slf.symbol.as_str().into() - } + use crate::error_reporting::traits::on_unimplemented_format::format; + format(&slf, args) } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs index 52643869d7800..b18642c785d94 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -2,6 +2,7 @@ use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_hir::attrs::diagnostic::*; use rustc_parse_format::{ParseMode, Parser, Piece, Position}; use rustc_span::{Ident, Symbol, kw, sym}; +use thin_vec::ThinVec; use crate::errors::InvalidOnClause; @@ -73,7 +74,7 @@ fn parse_predicate( fn parse_predicate_sequence( sequence: &[MetaItemInner], generics: &[Symbol], -) -> Result, InvalidOnClause> { +) -> Result, InvalidOnClause> { sequence.iter().map(|item| parse_predicate(item, generics)).collect() } @@ -99,7 +100,7 @@ fn parse_name(Ident { name, span }: Ident, generics: &[Symbol]) -> Result FilterFormatString { let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) .map(|p| match p { - Piece::Lit(s) => LitOrArg::Lit(s.to_owned()), + Piece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)), // We just ignore formatspecs here Piece::NextArgument(a) => match a.position { // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even @@ -109,15 +110,15 @@ fn parse_filter(input: Symbol) -> FilterFormatString { // // Don't try to format these later! Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { - LitOrArg::Lit(format!("{{{arg}}}")) + LitOrArg::Lit(Symbol::intern(&format!("{{{arg}}}"))) } // FIXME(mejrs) We should check if these correspond to a generic of the trait. - Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()), + Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)), // FIXME(mejrs) These should really be warnings/errors - Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")), - Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")), + Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces), + Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&format!("{{{idx}}}"))), }, }) .collect(); @@ -129,10 +130,9 @@ fn format_filter(slf: &FilterFormatString, generic_args: &[(Symbol, String)]) -> for piece in &slf.pieces { match piece { - LitOrArg::Lit(s) => ret.push_str(s), - LitOrArg::Arg(arg) => { - let s = Symbol::intern(arg); - match generic_args.iter().find(|(k, _)| *k == s) { + LitOrArg::Lit(s) => ret.push_str(s.as_str()), + LitOrArg::Arg(s) => { + match generic_args.iter().find(|(k, _)| k == s) { Some((_, val)) => ret.push_str(val), None => { // FIXME(mejrs) If we start checking as mentioned in diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 257f28121e40e..737778d917543 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -1,10 +1,10 @@ use std::fmt; use std::ops::Range; -use errors::*; use rustc_hir::attrs::diagnostic::*; +use rustc_hir::lints::FormatWarning; +use rustc_middle::ty::TyCtxt; use rustc_middle::ty::print::TraitRefPrintSugared; -use rustc_middle::ty::{GenericParamDefKind, TyCtxt}; use rustc_parse_format::{ Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; @@ -12,6 +12,7 @@ use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_FORMAT_LITERALS; use rustc_span::def_id::DefId; use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; +use crate::error_reporting::traits::on_unimplemented_format::errors::*; pub enum Ctx<'tcx> { // `#[rustc_on_unimplemented]` RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, @@ -21,20 +22,6 @@ pub enum Ctx<'tcx> { pub fn emit_warning<'tcx>(slf: &FormatWarning, tcx: TyCtxt<'tcx>, item_def_id: DefId) { match *slf { - FormatWarning::UnknownParam { argument_name, span } => { - let this = tcx.item_ident(item_def_id); - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - UnknownFormatParameterForOnUnimplementedAttr { - argument_name, - trait_name: this, - }, - ); - } - } FormatWarning::PositionalArgument { span, .. } => { if let Some(item_def_id) = item_def_id.as_local() { tcx.emit_node_span_lint( @@ -55,13 +42,6 @@ pub fn emit_warning<'tcx>(slf: &FormatWarning, tcx: TyCtxt<'tcx>, item_def_id: D ); } } - FormatWarning::FutureIncompat { .. } => { - // We've never deprecated anything in diagnostic namespace format strings - // but if we do we will emit a warning here - - // FIXME(mejrs) in a couple releases, start emitting warnings for - // #[rustc_on_unimplemented] deprecated args - } } } @@ -112,7 +92,7 @@ pub fn parse_format_string<'tcx>( snippet: Option, span: Span, ctx: &Ctx<'tcx>, -) -> Result { +) -> Result<(FormatString, Vec), ParseError> { let s = input.as_str(); let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); let pieces: Vec<_> = parser.by_ref().collect(); @@ -125,7 +105,7 @@ pub fn parse_format_string<'tcx>( let pieces = pieces .into_iter() .map(|piece| match piece { - RpfPiece::Lit(lit) => Piece::Lit(lit.into()), + RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)), RpfPiece::NextArgument(arg) => { warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); @@ -134,23 +114,26 @@ pub fn parse_format_string<'tcx>( }) .collect(); - Ok(FormatString { input, pieces, span, warnings }) + Ok((FormatString { input, pieces, span }, warnings)) } pub fn format(slf: &FormatString, args: &FormatArgs<'_>) -> String { let mut ret = String::new(); for piece in &slf.pieces { match piece { - Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(&s), + Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(s.as_str()), // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` - Piece::Arg(FormatArg::GenericParam { generic_param }) => { - // Should always be some but we can't raise errors here - let value = match args.generic_args.iter().find(|(p, _)| p == generic_param) { - Some((_, val)) => val.to_string(), - None => generic_param.to_string(), - }; - ret.push_str(&value); + Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { + match args.generic_args.iter().find(|(p, _)| p == generic_param) { + Some((_, val)) => ret.push_str(val.as_str()), + + None => { + // Apparently this was not actually a generic parameter, so lets write + // what the user wrote. + let _ = fmt::write(&mut ret, format_args!("{{{generic_param}}}")); + } + } } // `{Self}` Piece::Arg(FormatArg::SelfUpper) => { @@ -199,17 +182,7 @@ fn parse_arg<'tcx>( ( Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, generic_param, - ) if tcx.generics_of(trait_def_id).own_params.iter().any(|param| { - !matches!(param.kind, GenericParamDefKind::Lifetime) && param.name == generic_param - }) => - { - FormatArg::GenericParam { generic_param } - } - - (_, argument_name) => { - warnings.push(FormatWarning::UnknownParam { argument_name, span }); - FormatArg::AsIs(format!("{{{}}}", argument_name.as_str())) - } + ) => FormatArg::GenericParam { generic_param, span }, }, // `{:1}` and `{}` are ignored @@ -218,14 +191,14 @@ fn parse_arg<'tcx>( span, help: format!("use `{{{idx}}}` to print a number in braces"), }); - FormatArg::AsIs(format!("{{{idx}}}")) + FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}"))) } Position::ArgumentImplicitlyIs(_) => { warnings.push(FormatWarning::PositionalArgument { span, help: String::from("use `{{}}` to print empty braces"), }); - FormatArg::AsIs(String::from("{}")) + FormatArg::AsIs(sym::empty_braces) } } } diff --git a/tests/ui/attributes/malformed-attrs.stderr b/tests/ui/attributes/malformed-attrs.stderr index 01e70adf4ee99..004ea7d985aad 100644 --- a/tests/ui/attributes/malformed-attrs.stderr +++ b/tests/ui/attributes/malformed-attrs.stderr @@ -723,23 +723,6 @@ help: use `#[rustc_align(...)]` instead LL | #[repr] | ^^^^^^^ -warning: missing options for `on_unimplemented` attribute - --> $DIR/malformed-attrs.rs:142:1 - | -LL | #[diagnostic::on_unimplemented] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: at least one of the `message`, `note` and `label` options are expected - = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: malformed `on_unimplemented` attribute - --> $DIR/malformed-attrs.rs:144:1 - | -LL | #[diagnostic::on_unimplemented = 1] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - error: valid forms for the attribute are `#[doc = "string"]`, `#[doc(alias)]`, `#[doc(attribute)]`, `#[doc(auto_cfg)]`, `#[doc(cfg)]`, `#[doc(fake_variadic)]`, `#[doc(hidden)]`, `#[doc(html_favicon_url)]`, `#[doc(html_logo_url)]`, `#[doc(html_no_source)]`, `#[doc(html_playground_url)]`, `#[doc(html_root_url)]`, `#[doc(include)]`, `#[doc(inline)]`, `#[doc(issue_tracker_base_url)]`, `#[doc(keyword)]`, `#[doc(masked)]`, `#[doc(no_default_passes)]`, `#[doc(no_inline)]`, `#[doc(notable_trait)]`, `#[doc(passes)]`, `#[doc(plugins)]`, `#[doc(rust_logo)]`, `#[doc(search_unbox)]`, `#[doc(spotlight)]`, and `#[doc(test)]` --> $DIR/malformed-attrs.rs:41:1 | @@ -810,6 +793,23 @@ LL | #[no_implicit_prelude = 23] = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = help: `#[no_implicit_prelude]` can be applied to crates and modules +warning: missing options for `on_unimplemented` attribute + --> $DIR/malformed-attrs.rs:142:1 + | +LL | #[diagnostic::on_unimplemented] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: malformed `on_unimplemented` attribute + --> $DIR/malformed-attrs.rs:144:1 + | +LL | #[diagnostic::on_unimplemented = 1] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options + warning: `#[diagnostic::do_not_recommend]` does not expect any arguments --> $DIR/malformed-attrs.rs:151:1 | diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.rs b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.rs index 82c2db7e26d7f..56255a25b3f39 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.rs +++ b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.rs @@ -1,34 +1,27 @@ //@ reference: attributes.diagnostic.on_unimplemented.invalid-string #[diagnostic::on_unimplemented(message = "{{Test } thing")] //~^WARN unmatched `}` found -//~|WARN unmatched `}` found trait ImportantTrait1 {} #[diagnostic::on_unimplemented(message = "Test {}")] //~^WARN positional format arguments are not allowed here -//~|WARN positional format arguments are not allowed here trait ImportantTrait2 {} #[diagnostic::on_unimplemented(message = "Test {1:}")] //~^WARN positional format arguments are not allowed here -//~|WARN positional format arguments are not allowed here -//~|WARN invalid format specifier [malformed_diagnostic_format_literals] //~|WARN invalid format specifier [malformed_diagnostic_format_literals] trait ImportantTrait3 {} #[diagnostic::on_unimplemented(message = "Test {Self:123}")] //~^WARN invalid format specifier -//~|WARN invalid format specifier trait ImportantTrait4 {} #[diagnostic::on_unimplemented(message = "Test {Self:!}")] //~^WARN invalid format specifier [malformed_diagnostic_format_literals] -//~|WARN invalid format specifier [malformed_diagnostic_format_literals] trait ImportantTrait5 {} #[diagnostic::on_unimplemented(message = "Test {Self:}")] //~^WARN invalid format specifier [malformed_diagnostic_format_literals] -//~|WARN invalid format specifier [malformed_diagnostic_format_literals] trait ImportantTrait6 {} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr index 2138d591ca207..a6ab2f245a3e4 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/broken_format.stderr @@ -2,12 +2,14 @@ warning: unmatched `}` found --> $DIR/broken_format.rs:2:42 | LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")] - | ^^^^^^^^^^^^^^^^ + | ^^^^^^^^-^^^^^^^ + | | + | unmatched `}` | = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:7:49 + --> $DIR/broken_format.rs:6:49 | LL | #[diagnostic::on_unimplemented(message = "Test {}")] | ^ @@ -15,7 +17,7 @@ LL | #[diagnostic::on_unimplemented(message = "Test {}")] = help: only named format arguments with the name of one of the generic types are allowed in this context warning: invalid format specifier - --> $DIR/broken_format.rs:12:50 + --> $DIR/broken_format.rs:10:50 | LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] | ^ @@ -23,7 +25,7 @@ LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] = help: no format specifier are supported in this position warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:12:49 + --> $DIR/broken_format.rs:10:49 | LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] | ^ @@ -31,7 +33,7 @@ LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] = help: only named format arguments with the name of one of the generic types are allowed in this context warning: invalid format specifier - --> $DIR/broken_format.rs:19:53 + --> $DIR/broken_format.rs:15:53 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")] | ^^^^ @@ -39,7 +41,7 @@ LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")] = help: no format specifier are supported in this position warning: invalid format specifier - --> $DIR/broken_format.rs:24:53 + --> $DIR/broken_format.rs:19:53 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] | ^^ @@ -47,23 +49,15 @@ LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] = help: no format specifier are supported in this position warning: invalid format specifier - --> $DIR/broken_format.rs:29:53 + --> $DIR/broken_format.rs:23:53 | LL | #[diagnostic::on_unimplemented(message = "Test {Self:}")] | ^ | = help: no format specifier are supported in this position -warning: unmatched `}` found - --> $DIR/broken_format.rs:2:42 - | -LL | #[diagnostic::on_unimplemented(message = "{{Test } thing")] - | ^^^^^^^^^^^^^^^^ - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: {{Test } thing - --> $DIR/broken_format.rs:43:13 + --> $DIR/broken_format.rs:36:13 | LL | check_1(()); | ------- ^^ the trait `ImportantTrait1` is not implemented for `()` @@ -71,27 +65,18 @@ LL | check_1(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:5:1 + --> $DIR/broken_format.rs:4:1 | LL | trait ImportantTrait1 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_1` - --> $DIR/broken_format.rs:35:20 + --> $DIR/broken_format.rs:28:20 | LL | fn check_1(_: impl ImportantTrait1) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_1` -warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:7:49 - | -LL | #[diagnostic::on_unimplemented(message = "Test {}")] - | ^ - | - = help: only named format arguments with the name of one of the generic types are allowed in this context - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Test {} - --> $DIR/broken_format.rs:45:13 + --> $DIR/broken_format.rs:38:13 | LL | check_2(()); | ------- ^^ the trait `ImportantTrait2` is not implemented for `()` @@ -99,36 +84,18 @@ LL | check_2(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:10:1 + --> $DIR/broken_format.rs:8:1 | LL | trait ImportantTrait2 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_2` - --> $DIR/broken_format.rs:36:20 + --> $DIR/broken_format.rs:29:20 | LL | fn check_2(_: impl ImportantTrait2) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_2` -warning: invalid format specifier - --> $DIR/broken_format.rs:12:50 - | -LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] - | ^ - | - = help: no format specifier are supported in this position - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: positional format arguments are not allowed here - --> $DIR/broken_format.rs:12:49 - | -LL | #[diagnostic::on_unimplemented(message = "Test {1:}")] - | ^ - | - = help: only named format arguments with the name of one of the generic types are allowed in this context - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Test {1} - --> $DIR/broken_format.rs:47:13 + --> $DIR/broken_format.rs:40:13 | LL | check_3(()); | ------- ^^ the trait `ImportantTrait3` is not implemented for `()` @@ -136,27 +103,18 @@ LL | check_3(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:17:1 + --> $DIR/broken_format.rs:13:1 | LL | trait ImportantTrait3 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_3` - --> $DIR/broken_format.rs:37:20 + --> $DIR/broken_format.rs:30:20 | LL | fn check_3(_: impl ImportantTrait3) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_3` -warning: invalid format specifier - --> $DIR/broken_format.rs:19:53 - | -LL | #[diagnostic::on_unimplemented(message = "Test {Self:123}")] - | ^^^^ - | - = help: no format specifier are supported in this position - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Test () - --> $DIR/broken_format.rs:49:13 + --> $DIR/broken_format.rs:42:13 | LL | check_4(()); | ------- ^^ the trait `ImportantTrait4` is not implemented for `()` @@ -164,27 +122,18 @@ LL | check_4(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:22:1 + --> $DIR/broken_format.rs:17:1 | LL | trait ImportantTrait4 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_4` - --> $DIR/broken_format.rs:38:20 + --> $DIR/broken_format.rs:31:20 | LL | fn check_4(_: impl ImportantTrait4) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_4` -warning: invalid format specifier - --> $DIR/broken_format.rs:24:53 - | -LL | #[diagnostic::on_unimplemented(message = "Test {Self:!}")] - | ^^ - | - = help: no format specifier are supported in this position - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Test () - --> $DIR/broken_format.rs:51:13 + --> $DIR/broken_format.rs:44:13 | LL | check_5(()); | ------- ^^ the trait `ImportantTrait5` is not implemented for `()` @@ -192,27 +141,18 @@ LL | check_5(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:27:1 + --> $DIR/broken_format.rs:21:1 | LL | trait ImportantTrait5 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_5` - --> $DIR/broken_format.rs:39:20 + --> $DIR/broken_format.rs:32:20 | LL | fn check_5(_: impl ImportantTrait5) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_5` -warning: invalid format specifier - --> $DIR/broken_format.rs:29:53 - | -LL | #[diagnostic::on_unimplemented(message = "Test {Self:}")] - | ^ - | - = help: no format specifier are supported in this position - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Test () - --> $DIR/broken_format.rs:53:13 + --> $DIR/broken_format.rs:46:13 | LL | check_6(()); | ------- ^^ the trait `ImportantTrait6` is not implemented for `()` @@ -220,16 +160,16 @@ LL | check_6(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/broken_format.rs:32:1 + --> $DIR/broken_format.rs:25:1 | LL | trait ImportantTrait6 {} | ^^^^^^^^^^^^^^^^^^^^^ note: required by a bound in `check_6` - --> $DIR/broken_format.rs:40:20 + --> $DIR/broken_format.rs:33:20 | LL | fn check_6(_: impl ImportantTrait6) {} | ^^^^^^^^^^^^^^^ required by this bound in `check_6` -error: aborting due to 6 previous errors; 14 warnings emitted +error: aborting due to 6 previous errors; 7 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.rs b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.rs index a0e497fa045b3..e64e60f30c0c1 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.rs +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.rs @@ -5,22 +5,18 @@ #[diagnostic::on_unimplemented( on(Self = "&str"), //~^WARN malformed `on_unimplemented` attribute - //~|WARN malformed `on_unimplemented` attribute message = "trait has `{Self}` and `{T}` as params", label = "trait has `{Self}` and `{T}` as params", note = "trait has `{Self}` and `{T}` as params", parent_label = "in this scope", //~^WARN malformed `on_unimplemented` attribute - //~|WARN malformed `on_unimplemented` attribute append_const_msg //~^WARN malformed `on_unimplemented` attribute - //~|WARN malformed `on_unimplemented` attribute )] trait Foo {} #[diagnostic::on_unimplemented = "Message"] //~^WARN malformed `on_unimplemented` attribute -//~|WARN malformed `on_unimplemented` attribute trait Bar {} #[diagnostic::on_unimplemented(message = "Not allowed to apply it on a impl")] @@ -32,27 +28,16 @@ impl Bar for i32 {} #[diagnostic::on_unimplemented( message = "{from_desugaring}{direct}{cause}{integral}{integer}", //~^WARN there is no parameter `from_desugaring` on trait `Baz` - //~|WARN there is no parameter `from_desugaring` on trait `Baz` //~|WARN there is no parameter `direct` on trait `Baz` - //~|WARN there is no parameter `direct` on trait `Baz` - //~|WARN there is no parameter `cause` on trait `Baz` //~|WARN there is no parameter `cause` on trait `Baz` //~|WARN there is no parameter `integral` on trait `Baz` - //~|WARN there is no parameter `integral` on trait `Baz` - //~|WARN there is no parameter `integer` on trait `Baz` //~|WARN there is no parameter `integer` on trait `Baz` label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" //~^WARN there is no parameter `float` on trait `Baz` - //~|WARN there is no parameter `float` on trait `Baz` - //~|WARN there is no parameter `_Self` on trait `Baz` //~|WARN there is no parameter `_Self` on trait `Baz` //~|WARN there is no parameter `crate_local` on trait `Baz` - //~|WARN there is no parameter `crate_local` on trait `Baz` - //~|WARN there is no parameter `Trait` on trait `Baz` //~|WARN there is no parameter `Trait` on trait `Baz` //~|WARN there is no parameter `ItemContext` on trait `Baz` - //~|WARN there is no parameter `ItemContext` on trait `Baz` - //~|WARN there is no parameter `This` on trait `Baz` //~|WARN there is no parameter `This` on trait `Baz` )] trait Baz {} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr index eaf1de9ccadab..862f805e15ba7 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_accept_options_of_the_internal_rustc_attribute.stderr @@ -1,46 +1,13 @@ warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:26:1 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:22:1 | LL | #[diagnostic::on_unimplemented(message = "Not allowed to apply it on a impl")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:6:5 - | -LL | on(Self = "&str"), - | ^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:12:5 - | -LL | parent_label = "in this scope", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:15:5 - | -LL | append_const_msg - | ^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:21:32 - | -LL | #[diagnostic::on_unimplemented = "Message"] - | ^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - warning: there is no parameter `from_desugaring` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:29:17 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", | ^^^^^^^^^^^^^^^ @@ -49,7 +16,7 @@ LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: there is no parameter `direct` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:29:34 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", | ^^^^^^ @@ -57,7 +24,7 @@ LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `cause` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:29:42 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", | ^^^^^ @@ -65,7 +32,7 @@ LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `integral` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:29:49 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", | ^^^^^^^^ @@ -73,7 +40,7 @@ LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `integer` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:29:59 | LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", | ^^^^^^^ @@ -81,7 +48,7 @@ LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `float` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:15 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^^ @@ -89,7 +56,7 @@ LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `_Self` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:22 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^^ @@ -97,7 +64,7 @@ LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `crate_local` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:29 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^^^^^^^^ @@ -105,7 +72,7 @@ LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `Trait` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:42 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^^ @@ -113,7 +80,7 @@ LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `ItemContext` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:49 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^^^^^^^^ @@ -121,7 +88,7 @@ LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" = help: expect either a generic argument name or `{Self}` as format argument warning: there is no parameter `This` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:62 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:35:62 | LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" | ^^^^ @@ -135,28 +102,34 @@ LL | on(Self = "&str"), | ^^^^^^^^^^^^^^^^^ invalid option found here | = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:12:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:11:5 | LL | parent_label = "in this scope", | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here | = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:15:5 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:13:5 | LL | append_const_msg | ^^^^^^^^^^^^^^^^ invalid option found here | = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +warning: malformed `on_unimplemented` attribute + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:18:1 + | +LL | #[diagnostic::on_unimplemented = "Message"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here + | + = help: only `message`, `note` and `label` are allowed as options error[E0277]: trait has `()` and `i32` as params - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:65:15 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:50:15 | LL | takes_foo(()); | --------- ^^ trait has `()` and `i32` as params @@ -166,27 +139,18 @@ LL | takes_foo(()); = help: the trait `Foo` is not implemented for `()` = note: trait has `()` and `i32` as params help: this trait has no implementations, consider adding one - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:19:1 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:16:1 | LL | trait Foo {} | ^^^^^^^^^^^^ note: required by a bound in `takes_foo` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:60:22 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:45:22 | LL | fn takes_foo(_: impl Foo) {} | ^^^^^^^^ required by this bound in `takes_foo` -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:21:32 - | -LL | #[diagnostic::on_unimplemented = "Message"] - | ^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: the trait bound `(): Bar` is not satisfied - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:67:15 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:52:15 | LL | takes_bar(()); | --------- ^^ the trait `Bar` is not implemented for `()` @@ -194,117 +158,18 @@ LL | takes_bar(()); | required by a bound introduced by this call | help: the trait `Bar` is implemented for `i32` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:28:1 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:24:1 | LL | impl Bar for i32 {} | ^^^^^^^^^^^^^^^^ note: required by a bound in `takes_bar` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:61:22 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:46:22 | LL | fn takes_bar(_: impl Bar) {} | ^^^ required by this bound in `takes_bar` -warning: there is no parameter `from_desugaring` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:17 - | -LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `direct` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:34 - | -LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `cause` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:42 - | -LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `integral` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:49 - | -LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `integer` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:33:59 - | -LL | message = "{from_desugaring}{direct}{cause}{integral}{integer}", - | ^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `float` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:15 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `_Self` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:22 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `crate_local` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:29 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `Trait` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:42 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `ItemContext` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:49 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: there is no parameter `This` on trait `Baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:44:62 - | -LL | label = "{float}{_Self}{crate_local}{Trait}{ItemContext}{This}" - | ^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: {from_desugaring}{direct}{cause}{integral}{integer} - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:69:15 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:54:15 | LL | takes_baz(()); | --------- ^^ {float}{_Self}{crate_local}{Trait}{ItemContext}{This} @@ -313,16 +178,16 @@ LL | takes_baz(()); | = help: the trait `Baz` is not implemented for `()` help: this trait has no implementations, consider adding one - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:58:1 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:43:1 | LL | trait Baz {} | ^^^^^^^^^ note: required by a bound in `takes_baz` - --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:62:22 + --> $DIR/do_not_accept_options_of_the_internal_rustc_attribute.rs:47:22 | LL | fn takes_baz(_: impl Baz) {} | ^^^ required by this bound in `takes_baz` -error: aborting due to 3 previous errors; 31 warnings emitted +error: aborting due to 3 previous errors; 16 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs index 08eb5707e909e..c759acc12565c 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs @@ -2,7 +2,6 @@ //@ reference: attributes.diagnostic.on_unimplemented.unknown-keys #[diagnostic::on_unimplemented(unsupported = "foo")] //~^WARN malformed `on_unimplemented` attribute -//~|WARN malformed `on_unimplemented` attribute trait Foo {} #[diagnostic::on_unimplemented(message = "Baz")] @@ -11,12 +10,10 @@ struct Bar {} #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")] //~^WARN malformed `on_unimplemented` attribute -//~|WARN malformed `on_unimplemented` attribute trait Baz {} #[diagnostic::on_unimplemented(message = "Boom", on(Self = "i32", message = "whatever"))] //~^WARN malformed `on_unimplemented` attribute -//~|WARN malformed `on_unimplemented` attribute trait Boom {} #[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = "whatever"))] @@ -29,12 +26,10 @@ trait Doom {} #[diagnostic::on_unimplemented] //~^WARN missing options for `on_unimplemented` attribute -//~|WARN missing options for `on_unimplemented` attribute trait Whatever {} #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] //~^WARN there is no parameter `DoesNotExist` on trait `Test` -//~|WARN there is no parameter `DoesNotExist` on trait `Test` trait Test {} fn take_foo(_: impl Foo) {} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr index df5d0d6dccb40..4361e3261a0c1 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr @@ -1,11 +1,20 @@ warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:8:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:7:1 | LL | #[diagnostic::on_unimplemented(message = "Baz")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(misplaced_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default +warning: there is no parameter `DoesNotExist` on trait `Test` + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:44 + | +LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] + | ^^^^^^^^^^^^ + | + = help: expect either a generic argument name or `{Self}` as format argument + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + warning: malformed `on_unimplemented` attribute --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:3:32 | @@ -16,7 +25,7 @@ LL | #[diagnostic::on_unimplemented(unsupported = "foo")] = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:12:50 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:11:50 | LL | #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")] | ^^^^^^^^^^^^^^^^^^^ invalid option found here @@ -24,7 +33,7 @@ LL | #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")] = help: only `message`, `note` and `label` are allowed as options warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:17:50 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:15:50 | LL | #[diagnostic::on_unimplemented(message = "Boom", on(Self = "i32", message = "whatever"))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here @@ -32,7 +41,7 @@ LL | #[diagnostic::on_unimplemented(message = "Boom", on(Self = "i32", message = = help: only `message`, `note` and `label` are allowed as options warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:22:50 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:19:50 | LL | #[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = "whatever"))] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here @@ -40,41 +49,23 @@ LL | #[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = help: only `message`, `note` and `label` are allowed as options warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:26:32 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:23:1 | LL | #[diagnostic::on_unimplemented = "boom"] - | ^^^^^^^^ invalid option found here + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here | = help: only `message`, `note` and `label` are allowed as options warning: missing options for `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:30:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:27:1 | LL | #[diagnostic::on_unimplemented] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: at least one of the `message`, `note` and `label` options are expected -warning: there is no parameter `DoesNotExist` on trait `Test` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:35:44 - | -LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] - | ^^^^^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:3:32 - | -LL | #[diagnostic::on_unimplemented(unsupported = "foo")] - | ^^^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: the trait bound `i32: Foo` is not satisfied - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:47:14 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:42:14 | LL | take_foo(1_i32); | -------- ^^^^^ the trait `Foo` is not implemented for `i32` @@ -82,27 +73,18 @@ LL | take_foo(1_i32); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:6:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:5:1 | LL | trait Foo {} | ^^^^^^^^^ note: required by a bound in `take_foo` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:40:21 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:35:21 | LL | fn take_foo(_: impl Foo) {} | ^^^ required by this bound in `take_foo` -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:12:50 - | -LL | #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")] - | ^^^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Boom - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:49:14 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:44:14 | LL | take_baz(1_i32); | -------- ^^^^^ the trait `Baz` is not implemented for `i32` @@ -110,27 +92,18 @@ LL | take_baz(1_i32); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:15:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:13:1 | LL | trait Baz {} | ^^^^^^^^^ note: required by a bound in `take_baz` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:41:21 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:36:21 | LL | fn take_baz(_: impl Baz) {} | ^^^ required by this bound in `take_baz` -warning: malformed `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:17:50 - | -LL | #[diagnostic::on_unimplemented(message = "Boom", on(Self = "i32", message = "whatever"))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: Boom - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:51:15 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:46:15 | LL | take_boom(1_i32); | --------- ^^^^^ the trait `Boom` is not implemented for `i32` @@ -138,27 +111,18 @@ LL | take_boom(1_i32); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:20:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:17:1 | LL | trait Boom {} | ^^^^^^^^^^ note: required by a bound in `take_boom` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:42:22 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:37:22 | LL | fn take_boom(_: impl Boom) {} | ^^^^ required by this bound in `take_boom` -warning: missing options for `on_unimplemented` attribute - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:30:1 - | -LL | #[diagnostic::on_unimplemented] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | - = help: at least one of the `message`, `note` and `label` options are expected - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: the trait bound `i32: Whatever` is not satisfied - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:53:19 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:48:19 | LL | take_whatever(1_i32); | ------------- ^^^^^ the trait `Whatever` is not implemented for `i32` @@ -166,27 +130,18 @@ LL | take_whatever(1_i32); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:33:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:29:1 | LL | trait Whatever {} | ^^^^^^^^^^^^^^ note: required by a bound in `take_whatever` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:43:26 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:38:26 | LL | fn take_whatever(_: impl Whatever) {} | ^^^^^^^^ required by this bound in `take_whatever` -warning: there is no parameter `DoesNotExist` on trait `Test` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:35:44 - | -LL | #[diagnostic::on_unimplemented(message = "{DoesNotExist}")] - | ^^^^^^^^^^^^ - | - = help: expect either a generic argument name or `{Self}` as format argument - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error[E0277]: {DoesNotExist} - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:55:15 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:50:15 | LL | take_test(()); | --------- ^^ the trait `Test` is not implemented for `()` @@ -194,16 +149,16 @@ LL | take_test(()); | required by a bound introduced by this call | help: this trait has no implementations, consider adding one - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:38:1 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:33:1 | LL | trait Test {} | ^^^^^^^^^^ note: required by a bound in `take_test` - --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:44:22 + --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:39:22 | LL | fn take_test(_: impl Test) {} | ^^^^ required by this bound in `take_test` -error: aborting due to 5 previous errors; 13 warnings emitted +error: aborting due to 5 previous errors; 8 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs index b06f56bd66e44..7d1ed6cf3a50c 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs +++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs @@ -3,13 +3,11 @@ #[diagnostic::on_unimplemented( if(Self = "()"), //~^WARN malformed `on_unimplemented` attribute - //~|WARN malformed `on_unimplemented` attribute message = "custom message", note = "custom note" )] #[diagnostic::on_unimplemented(message = "fallback!!")] //~^ WARN `message` is ignored due to previous definition of `message` -//~| WARN `message` is ignored due to previous definition of `message` #[diagnostic::on_unimplemented(label = "fallback label")] #[diagnostic::on_unimplemented(note = "fallback note")] trait Foo {} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr index 24b3a9c3405fa..2947ca23a0679 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr @@ -1,20 +1,13 @@ -warning: malformed `on_unimplemented` attribute +error: expected identifier, found keyword `if` --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:4:5 | LL | if(Self = "()"), - | ^^^^^^^^^^^^^^^ invalid option found here + | ^^ expected identifier, found keyword | - = help: only `message`, `note` and `label` are allowed as options - = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: `message` is ignored due to previous definition of `message` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:10:32 +help: escape `if` to use it as an identifier | -LL | message = "custom message", - | -------------------------- `message` is first declared here -... -LL | #[diagnostic::on_unimplemented(message = "fallback!!")] - | ^^^^^^^^^^^^^^^^^^^^^^ `message` is already declared here +LL | r#if(Self = "()"), + | ++ warning: malformed `on_unimplemented` attribute --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:4:5 @@ -23,21 +16,19 @@ LL | if(Self = "()"), | ^^^^^^^^^^^^^^^ invalid option found here | = help: only `message`, `note` and `label` are allowed as options - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: `message` is ignored due to previous definition of `message` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:10:32 + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:9:32 | LL | message = "custom message", | -------------------------- `message` is first declared here ... LL | #[diagnostic::on_unimplemented(message = "fallback!!")] - | ^^^^^^^^^^^^^^^^^^^^^^ `message` is already declared here - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + | ^^^^^^^^^^^^^^^^^^^^^^ `message` is later redundantly declared here error[E0277]: custom message - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:20:15 + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:18:15 | LL | takes_foo(()); | --------- ^^ fallback label @@ -48,16 +39,16 @@ LL | takes_foo(()); = note: custom note = note: fallback note help: this trait has no implementations, consider adding one - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:1 + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:13:1 | LL | trait Foo {} | ^^^^^^^^^ note: required by a bound in `takes_foo` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:17:22 + --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:22 | LL | fn takes_foo(_: impl Foo) {} | ^^^ required by this bound in `takes_foo` -error: aborting due to 1 previous error; 4 warnings emitted +error: aborting due to 2 previous errors; 2 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.rs b/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.rs index d0eb608c40f20..414b7ec93b51f 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.rs +++ b/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.rs @@ -7,10 +7,8 @@ #[diagnostic::on_unimplemented( message = "second message", //~^WARN `message` is ignored due to previous definition of `message` - //~|WARN `message` is ignored due to previous definition of `message` label = "second label", //~^WARN `label` is ignored due to previous definition of `label` - //~|WARN `label` is ignored due to previous definition of `label` note = "second note" )] trait Foo {} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.stderr index de43656ff085b..2f20777c8f2f5 100644 --- a/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.stderr +++ b/tests/ui/diagnostic_namespace/on_unimplemented/report_warning_on_duplicated_options.stderr @@ -5,43 +5,21 @@ LL | message = "first message", | ------------------------- `message` is first declared here ... LL | message = "second message", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `message` is already declared here + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `message` is later redundantly declared here | = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default warning: `label` is ignored due to previous definition of `label` - --> $DIR/report_warning_on_duplicated_options.rs:11:5 + --> $DIR/report_warning_on_duplicated_options.rs:10:5 | LL | label = "first label", | --------------------- `label` is first declared here ... LL | label = "second label", - | ^^^^^^^^^^^^^^^^^^^^^^ `label` is already declared here - -warning: `message` is ignored due to previous definition of `message` - --> $DIR/report_warning_on_duplicated_options.rs:8:5 - | -LL | message = "first message", - | ------------------------- `message` is first declared here -... -LL | message = "second message", - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ `message` is already declared here - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - -warning: `label` is ignored due to previous definition of `label` - --> $DIR/report_warning_on_duplicated_options.rs:11:5 - | -LL | label = "first label", - | --------------------- `label` is first declared here -... -LL | label = "second label", - | ^^^^^^^^^^^^^^^^^^^^^^ `label` is already declared here - | - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + | ^^^^^^^^^^^^^^^^^^^^^^ `label` is later redundantly declared here error[E0277]: first message - --> $DIR/report_warning_on_duplicated_options.rs:22:15 + --> $DIR/report_warning_on_duplicated_options.rs:20:15 | LL | takes_foo(()); | --------- ^^ first label @@ -52,16 +30,16 @@ LL | takes_foo(()); = note: custom note = note: second note help: this trait has no implementations, consider adding one - --> $DIR/report_warning_on_duplicated_options.rs:16:1 + --> $DIR/report_warning_on_duplicated_options.rs:14:1 | LL | trait Foo {} | ^^^^^^^^^ note: required by a bound in `takes_foo` - --> $DIR/report_warning_on_duplicated_options.rs:19:22 + --> $DIR/report_warning_on_duplicated_options.rs:17:22 | LL | fn takes_foo(_: impl Foo) {} | ^^^ required by this bound in `takes_foo` -error: aborting due to 1 previous error; 4 warnings emitted +error: aborting due to 1 previous error; 2 warnings emitted For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr index 3fc5453277404..1777dc849d0c1 100644 --- a/tests/ui/on-unimplemented/bad-annotation.stderr +++ b/tests/ui/on-unimplemented/bad-annotation.stderr @@ -11,12 +11,6 @@ LL | #[rustc_on_unimplemented = "message"] LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")] | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -error[E0230]: cannot find parameter C on this trait - --> $DIR/bad-annotation.rs:19:90 - | -LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] - | ^ - error[E0231]: positional format arguments are not allowed here --> $DIR/bad-annotation.rs:23:90 | @@ -137,7 +131,7 @@ error[E0232]: invalid name in `on`-clause LL | #[rustc_on_unimplemented(on(abc = "y", message = "y"))] | ^^^ expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `abc` -error: aborting due to 20 previous errors +error: aborting due to 19 previous errors -Some errors have detailed explanations: E0230, E0231, E0232. -For more information about an error, try `rustc --explain E0230`. +Some errors have detailed explanations: E0231, E0232. +For more information about an error, try `rustc --explain E0231`. diff --git a/tests/ui/unpretty/diagnostic-attr.stdout b/tests/ui/unpretty/diagnostic-attr.stdout index 0b4b5f9193435..d1bdb90ee9c1f 100644 --- a/tests/ui/unpretty/diagnostic-attr.stdout +++ b/tests/ui/unpretty/diagnostic-attr.stdout @@ -5,9 +5,14 @@ use ::std::prelude::rust_2015::*; //@ check-pass //@ edition: 2015 -#[diagnostic::on_unimplemented(message = -"My Message for `ImportantTrait<{A}>` implemented for `{Self}`", label = -"My Label", note = "Note 1", note = "Note 2")] +#[attr = OnUnimplemented {directive: OnUnimplementedDirective {subcommands: [], +message: FormatString {input: "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", +pieces: [Lit("My Message for `ImportantTrait<"), +Arg(GenericParam {generic_param: "A"}), Lit(">` implemented for `"), +Arg(SelfUpper), Lit("`")]}, label: FormatString {input: "My Label", +pieces: [Lit("My Label")]}, notes: [FormatString {input: "Note 1", +pieces: [Lit("Note 1")]}, FormatString {input: "Note 2", +pieces: [Lit("Note 2")]}]}}] trait ImportantTrait { } #[attr = DoNotRecommend] From 668e758897eedff89e9fae1aded06df1ac5f5eef Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 8 Feb 2026 20:41:49 +0100 Subject: [PATCH 12/32] Due to attr parsing port, we do not attempt to recover from invalid syntax --- ...ed_options_and_continue_to_use_fallback.rs | 20 ------- ...ptions_and_continue_to_use_fallback.stderr | 54 ------------------- 2 files changed, 74 deletions(-) delete mode 100644 tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs delete mode 100644 tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs deleted file mode 100644 index 7d1ed6cf3a50c..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.rs +++ /dev/null @@ -1,20 +0,0 @@ -//@ reference: attributes.diagnostic.on_unimplemented.repetition -//@ reference: attributes.diagnostic.on_unimplemented.syntax -#[diagnostic::on_unimplemented( - if(Self = "()"), - //~^WARN malformed `on_unimplemented` attribute - message = "custom message", - note = "custom note" -)] -#[diagnostic::on_unimplemented(message = "fallback!!")] -//~^ WARN `message` is ignored due to previous definition of `message` -#[diagnostic::on_unimplemented(label = "fallback label")] -#[diagnostic::on_unimplemented(note = "fallback note")] -trait Foo {} - -fn takes_foo(_: impl Foo) {} - -fn main() { - takes_foo(()); - //~^ERROR custom message -} diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr deleted file mode 100644 index 2947ca23a0679..0000000000000 --- a/tests/ui/diagnostic_namespace/on_unimplemented/ignore_unsupported_options_and_continue_to_use_fallback.stderr +++ /dev/null @@ -1,54 +0,0 @@ -error: expected identifier, found keyword `if` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:4:5 - | -LL | if(Self = "()"), - | ^^ expected identifier, found keyword - | -help: escape `if` to use it as an identifier - | -LL | r#if(Self = "()"), - | ++ - -warning: malformed `on_unimplemented` attribute - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:4:5 - | -LL | if(Self = "()"), - | ^^^^^^^^^^^^^^^ invalid option found here - | - = help: only `message`, `note` and `label` are allowed as options - = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default - -warning: `message` is ignored due to previous definition of `message` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:9:32 - | -LL | message = "custom message", - | -------------------------- `message` is first declared here -... -LL | #[diagnostic::on_unimplemented(message = "fallback!!")] - | ^^^^^^^^^^^^^^^^^^^^^^ `message` is later redundantly declared here - -error[E0277]: custom message - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:18:15 - | -LL | takes_foo(()); - | --------- ^^ fallback label - | | - | required by a bound introduced by this call - | - = help: the trait `Foo` is not implemented for `()` - = note: custom note - = note: fallback note -help: this trait has no implementations, consider adding one - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:13:1 - | -LL | trait Foo {} - | ^^^^^^^^^ -note: required by a bound in `takes_foo` - --> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:22 - | -LL | fn takes_foo(_: impl Foo) {} - | ^^^ required by this bound in `takes_foo` - -error: aborting due to 2 previous errors; 2 warnings emitted - -For more information about this error, try `rustc --explain E0277`. From ff542935b816be73836cfeba312a847ae8dd1034 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:00:00 +0100 Subject: [PATCH 13/32] Port rustc_on_unimplemented attribute --- .../src/attributes/diagnostic/mod.rs | 206 +++++++++++++- .../attributes/diagnostic/on_unimplemented.rs | 268 +++++++++++++----- compiler/rustc_hir/src/attrs/diagnostic.rs | 58 +++- .../rustc_hir_analysis/src/check/check.rs | 1 - compiler/rustc_lint/src/lints.rs | 2 +- compiler/rustc_lint_defs/src/lib.rs | 2 +- compiler/rustc_passes/src/check_attr.rs | 4 +- compiler/rustc_passes/src/errors.rs | 12 +- .../traits/on_unimplemented.rs | 102 +------ .../traits/on_unimplemented_condition.rs | 130 +-------- .../traits/on_unimplemented_format.rs | 11 - compiler/rustc_trait_selection/src/errors.rs | 55 ---- ...ssue-59523-on-implemented-is-not-unused.rs | 3 +- tests/ui/on-unimplemented/bad-annotation.rs | 32 ++- .../ui/on-unimplemented/bad-annotation.stderr | 109 +++---- .../feature-gate-on-unimplemented.rs | 2 +- .../feature-gate-on-unimplemented.stderr | 4 +- tests/ui/on-unimplemented/on-trait.rs | 4 +- tests/ui/unpretty/diagnostic-attr.stdout | 3 +- 19 files changed, 558 insertions(+), 450 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 280691f308eec..d6fce0d1f0567 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -1,18 +1,26 @@ #![allow(warnings)] - use std::ops::Range; -use rustc_hir::attrs::diagnostic::{FormatArg, FormatString, Piece}; +use rustc_errors::E0232; +use rustc_hir::AttrPath; +use rustc_hir::attrs::diagnostic::{ + FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, + OnUnimplementedCondition, Piece, Predicate, +}; use rustc_hir::lints::FormatWarning; +use rustc_macros::Diagnostic; use rustc_parse_format::{ Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; -use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; +use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym}; +use thin_vec::ThinVec; + +use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser}; pub mod on_unimplemented; #[derive(Copy, Clone)] -pub(crate) enum Ctx { +pub(crate) enum Mode { // `#[rustc_on_unimplemented]` RustcOnUnimplemented, // `#[diagnostic::...]` @@ -23,7 +31,7 @@ pub(crate) fn parse_format_string( input: Symbol, snippet: Option, span: Span, - ctx: Ctx, + mode: Mode, ) -> Result<(FormatString, Vec), ParseError> { let s = input.as_str(); let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); @@ -40,7 +48,7 @@ pub(crate) fn parse_format_string( RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)), RpfPiece::NextArgument(arg) => { warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); - let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); + let arg = parse_arg(&arg, mode, &mut warnings, span, parser.is_source_literal); Piece::Arg(arg) } }) @@ -51,7 +59,7 @@ pub(crate) fn parse_format_string( fn parse_arg( arg: &Argument<'_>, - ctx: Ctx, + mode: Mode, warnings: &mut Vec, input_span: Span, is_source_literal: bool, @@ -60,18 +68,18 @@ fn parse_arg( match arg.position { // Something like "hello {name}" - Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) { + Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) { // Only `#[rustc_on_unimplemented]` can use these - (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, - (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, - (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, + (Mode::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, + (Mode::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, + (Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, // Any attribute can use these ( - Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. }, kw::SelfUpper, ) => FormatArg::SelfUpper, ( - Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, + Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. }, generic_param, ) => FormatArg::GenericParam { generic_param, span }, }, @@ -115,3 +123,175 @@ fn warn_on_format_spec( fn slice_span(input: Span, Range { start, end }: Range, is_source_literal: bool) -> Span { if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input } } + +pub(crate) fn parse_condition( + input: &MetaItemOrLitParser, +) -> Result { + let span = input.span(); + let pred = parse_predicate(input)?; + Ok(OnUnimplementedCondition { span, pred }) +} + +fn parse_predicate(input: &MetaItemOrLitParser) -> Result { + let Some(meta_item) = input.meta_item() else { + return Err(InvalidOnClause::UnsupportedLiteral { span: input.span() }); + }; + + let Some(predicate) = meta_item.ident() else { + return Err(InvalidOnClause::ExpectedIdentifier { + span: meta_item.path().span(), + path: meta_item.path().get_attribute_path(), + }); + }; + + match meta_item.args() { + ArgParser::List(mis) => match predicate.name { + sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis)?)), + sym::all => Ok(Predicate::All(parse_predicate_sequence(mis)?)), + sym::not => { + if let Some(single) = mis.single() { + Ok(Predicate::Not(Box::new(parse_predicate(single)?))) + } else { + Err(InvalidOnClause::ExpectedOnePredInNot { span: mis.span }) + } + } + invalid_pred => { + Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred }) + } + }, + ArgParser::NameValue(p) => { + let Some(value) = p.value_as_ident() else { + return Err(InvalidOnClause::UnsupportedLiteral { span: p.args_span() }); + }; + let name = parse_name(predicate); + let value = parse_filter(value.name); + let kv = NameValue { name, value }; + Ok(Predicate::Match(kv)) + } + ArgParser::NoArgs => { + let flag = parse_flag(predicate)?; + Ok(Predicate::Flag(flag)) + } + } +} + +fn parse_predicate_sequence( + sequence: &MetaItemListParser, +) -> Result, InvalidOnClause> { + sequence.mixed().map(parse_predicate).collect() +} + +fn parse_flag(Ident { name, span }: Ident) -> Result { + match name { + sym::crate_local => Ok(Flag::CrateLocal), + sym::direct => Ok(Flag::Direct), + sym::from_desugaring => Ok(Flag::FromDesugaring), + invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }), + } +} + +fn parse_name(Ident { name, span }: Ident) -> Name { + match name { + kw::SelfUpper => Name::SelfUpper, + sym::from_desugaring => Name::FromDesugaring, + sym::cause => Name::Cause, + generic => Name::GenericArg(generic), + } +} + +fn parse_filter(input: Symbol) -> FilterFormatString { + let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) + .map(|p| match p { + RpfPiece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)), + // We just ignore formatspecs here + RpfPiece::NextArgument(a) => match a.position { + // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even + // if the integer type has been resolved, to allow targeting all integers. + // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet, + // from the `Display` impl of `InferTy` to be precise. + // + // Don't try to format these later! + Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { + LitOrArg::Lit(Symbol::intern(&format!("{{{arg}}}"))) + } + + // FIXME(mejrs) We should check if these correspond to a generic of the trait. + Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)), + + // FIXME(mejrs) These should really be warnings/errors + Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces), + Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&format!("{{{idx}}}"))), + }, + }) + .collect(); + FilterFormatString { pieces } +} + +#[derive(Diagnostic)] +pub(crate) enum InvalidOnClause { + #[diag("empty `on`-clause in `#[rustc_on_unimplemented]`", code = E0232)] + Empty { + #[primary_span] + #[label("empty `on`-clause here")] + span: Span, + }, + #[diag("expected a single predicate in `not(..)`", code = E0232)] + ExpectedOnePredInNot { + #[primary_span] + #[label("unexpected quantity of predicates here")] + span: Span, + }, + #[diag("literals inside `on`-clauses are not supported", code = E0232)] + UnsupportedLiteral { + #[primary_span] + #[label("unexpected literal here")] + span: Span, + }, + #[diag("expected an identifier inside this `on`-clause", code = E0232)] + ExpectedIdentifier { + #[primary_span] + #[label("expected an identifier here, not `{$path}`")] + span: Span, + path: AttrPath, + }, + #[diag("this predicate is invalid", code = E0232)] + InvalidPredicate { + #[primary_span] + #[label("expected one of `any`, `all` or `not` here, not `{$invalid_pred}`")] + span: Span, + invalid_pred: Symbol, + }, + #[diag("invalid flag in `on`-clause", code = E0232)] + InvalidFlag { + #[primary_span] + #[label( + "expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`" + )] + span: Span, + invalid_flag: Symbol, + }, + #[diag("invalid name in `on`-clause", code = E0232)] + InvalidName { + #[primary_span] + #[label( + "expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `{$invalid_name}`" + )] + span: Span, + invalid_name: Symbol, + }, +} + +#[derive(Diagnostic)] +#[diag("this attribute must have a value", code = E0232)] +#[note("e.g. `#[rustc_on_unimplemented(message=\"foo\")]`")] +pub struct NoValueInOnUnimplemented { + #[primary_span] + #[label("expected value here")] + pub span: Span, +} + +#[derive(Diagnostic)] +#[diag( + "using multiple `rustc_on_unimplemented` (or mixing it with `diagnostic::on_unimplemented`) is not supported" +)] +pub struct DupesNotAllowed; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 477cc5e14eb08..21db29aef15aa 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -1,5 +1,4 @@ -#![allow(warnings)] -use rustc_hir::attrs::diagnostic::OnUnimplementedDirective; +use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedDirective}; use rustc_hir::lints::AttributeLintKind; use rustc_session::lint::builtin::{ MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, @@ -16,38 +15,54 @@ pub struct OnUnimplementedParser { directive: Option<(Span, OnUnimplementedDirective)>, } +impl OnUnimplementedParser { + fn parse<'sess, S: Stage>( + &mut self, + cx: &mut AcceptContext<'_, 'sess, S>, + args: &ArgParser, + mode: Mode, + ) { + let span = cx.attr_span; + + let items = match args { + ArgParser::List(items) if items.len() != 0 => items, + ArgParser::NoArgs | ArgParser::List(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnUnimplemented, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + return; + } + }; + + let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) else { + return; + }; + merge_directives(cx, &mut self.directive, (span, directive)); + } +} + impl AttributeParser for OnUnimplementedParser { const ATTRIBUTES: AcceptMapping = &[ (&[sym::diagnostic, sym::on_unimplemented], template!(Word), |this, cx, args| { - let span = cx.attr_span; - - let items = match args { - ArgParser::List(items) => items, - ArgParser::NoArgs => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MissingOptionsForOnUnimplemented, - span, - ); - return; - } - ArgParser::NameValue(_) => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnimplementedAttr { span }, - span, - ); - return; - } - }; - - let Some(directive) = parse_directive_items(cx, Ctx::DiagnosticOnUnimplemented, items) - else { - return; - }; - merge_directives(cx, &mut this.directive, (span, directive)); + this.parse(cx, args, Mode::DiagnosticOnUnimplemented); }), - // todo (&[sym::rustc_on_unimplemented], template!(Word), |this, cx, args| {}), + ( + &[sym::rustc_on_unimplemented], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + this.parse(cx, args, Mode::RustcOnUnimplemented); + }, + ), ]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); @@ -65,8 +80,12 @@ fn merge_directives( later: (Span, OnUnimplementedDirective), ) { if let Some((first_span, first)) = first { - merge(cx, &mut first.message, later.1.message, "message"); - merge(cx, &mut first.label, later.1.label, "label"); + if first.is_rustc_attr || later.1.is_rustc_attr { + cx.emit_err(DupesNotAllowed); + } + + merge(cx, &mut first.message, later.1.message, sym::message); + merge(cx, &mut first.label, later.1.label, sym::label); first.notes.extend(later.1.notes); } else { *first = Some(later); @@ -77,7 +96,7 @@ fn merge( cx: &mut AcceptContext<'_, '_, S>, first: &mut Option<(Span, T)>, later: Option<(Span, T)>, - option_name: &'static str, + option_name: Symbol, ) { match (first, later) { (Some(_) | None, None) => {} @@ -98,42 +117,82 @@ fn merge( } } -fn parse_directive_items( +fn parse_directive_items<'p, S: Stage>( cx: &mut AcceptContext, - ctx: Ctx, - items: &MetaItemListParser, + mode: Mode, + items: impl Iterator, + is_root: bool, ) -> Option { let condition = None; - let mut message = None; - let mut label = None; + let mut message: Option<(Span, _)> = None; + let mut label: Option<(Span, _)> = None; let mut notes = ThinVec::new(); let mut parent_label = None; let mut subcommands = ThinVec::new(); let mut append_const_msg = None; - for item in items.mixed() { - // At this point, we are expecting any of: - // message = "..", label = "..", note = ".." - let Some((name, value, value_span)) = (try { - let item = item.meta_item()?; - let name = item.ident()?.name; - let nv = item.args().name_value()?; - let value = nv.value_as_str()?; - (name, value, nv.value_span) - }) else { - let span = item.span(); - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnimplementedAttr { span }, - span, - ); + for item in items { + let span = item.span(); + + macro malformed() {{ + match mode { + Mode::RustcOnUnimplemented => { + cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); + } + Mode::DiagnosticOnUnimplemented => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + } + } continue; + }} + + macro or_malformed($($code:tt)*) {{ + let Some(ret) = (try { + $($code)* + }) else { + + malformed!() + }; + ret + }} + + macro duplicate($name: ident, $($first_span:tt)*) {{ + match mode { + Mode::RustcOnUnimplemented => { + cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); + } + Mode::DiagnosticOnUnimplemented => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::IgnoredDiagnosticOption { + first_span: $($first_span)*, + later_span: span, + option_name: $name, + }, + span, + ); + } + } + }} + + let item: &MetaItemParser = or_malformed!(item.meta_item()?); + let name = or_malformed!(item.ident()?).name; + + // Some things like `message = "message"` must have a value. + // But with things like `append_const_msg` that is optional. + let value: Option = match item.args().name_value() { + Some(nv) => Some(or_malformed!(nv.value_as_ident()?)), + None => None, }; - let mut parse = |input| { - let snippet = cx.sess.source_map().span_to_snippet(value_span).ok(); + let mut parse_format = |input: Ident| { + let snippet = cx.sess.source_map().span_to_snippet(input.span).ok(); let is_snippet = snippet.is_some(); - match parse_format_string(input, snippet, value_span, ctx) { + match parse_format_string(input.name, snippet, input.span, mode) { Ok((f, warnings)) => { for warning in warnings { let (FormatWarning::InvalidSpecifier { span, .. } @@ -153,44 +212,101 @@ fn parse_directive_items( AttributeLintKind::DiagnosticWrappedParserError { description: e.description, label: e.label, - span: slice_span(value_span, e.span, is_snippet), + span: slice_span(input.span, e.span, is_snippet), }, - value_span, + input.span, ); // We could not parse the input, just use it as-is. - FormatString { input, span: value_span, pieces: thin_vec![Piece::Lit(input)] } + FormatString { + input: input.name, + span: input.span, + pieces: thin_vec![Piece::Lit(input.name)], + } } } }; - match name { - sym::message => { - if message.is_none() { - message.insert((item.span(), parse(value))); + match (mode, name) { + (_, sym::message) => { + let value = or_malformed!(value?); + if let Some(message) = &message { + duplicate!(name, message.0) } else { - // warn + message.insert((item.span(), parse_format(value))); + } + } + (_, sym::label) => { + let value = or_malformed!(value?); + if let Some(label) = &label { + duplicate!(name, label.0) + } else { + label.insert((item.span(), parse_format(value))); } } - sym::label => { - if label.is_none() { - label.insert((item.span(), parse(value))); + (_, sym::note) => { + let value = or_malformed!(value?); + notes.push(parse_format(value)) + } + + (Mode::RustcOnUnimplemented, sym::append_const_msg) => { + append_const_msg = if let Some(msg) = value { + Some(AppendConstMessage::Custom(msg.name, item.span())) + } else { + Some(AppendConstMessage::Default) + } + } + (Mode::RustcOnUnimplemented, sym::parent_label) => { + let value = or_malformed!(value?); + if parent_label.is_none() { + parent_label.insert(parse_format(value)); } else { // warn } } - sym::note => notes.push(parse(value)), + (Mode::RustcOnUnimplemented, sym::on) => { + if is_root { + let mut items = or_malformed!(item.args().list()?); + let mut iter = items.mixed(); + let condition: &MetaItemOrLitParser = match iter.next() { + Some(c) => c, + None => { + cx.emit_err(InvalidOnClause::Empty { span }); + continue; + } + }; + + let condition = parse_condition(condition); + + if items.len() < 2 { + // Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]` + // There's a condition but no directive behind it, this is a mistake. + malformed!(); + } + + let mut directive = + or_malformed!(parse_directive_items(cx, mode, iter, false)?); + + match condition { + Ok(c) => { + directive.condition = Some(c); + subcommands.push(directive); + } + Err(e) => { + cx.emit_err(e); + } + } + } else { + malformed!(); + } + } + _other => { - let span = item.span(); - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnimplementedAttr { span }, - span, - ); - continue; + malformed!(); } } } Some(OnUnimplementedDirective { + is_rustc_attr: matches!(mode, Mode::RustcOnUnimplemented), condition, subcommands, message, diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 95b10865b34c6..dc7461cf5de1b 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -8,6 +8,7 @@ use crate::attrs::PrintAttribute; #[derive(Clone, Default, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub struct OnUnimplementedDirective { + pub is_rustc_attr: bool, pub condition: Option, pub subcommands: ThinVec, pub message: Option<(Span, FormatString)>, @@ -18,7 +19,20 @@ pub struct OnUnimplementedDirective { } impl OnUnimplementedDirective { + /// Visit all the generic arguments used in the attribute, to see whether they are actually a + /// generic of the item. If not then `visit` must issue a diagnostic. + /// + /// We can't check this while parsing the attribute because `rustc_attr_parsing` doesn't have + /// access to the item an attribute is on. Instead we later call this function in `check_attr`. pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { + if let Some(condition) = &self.condition { + condition.visit_params(visit); + } + + for subcommand in &self.subcommands { + subcommand.visit_params(visit); + } + if let Some((_, message)) = &self.message { message.visit_params(visit); } @@ -26,13 +40,13 @@ impl OnUnimplementedDirective { label.visit_params(visit); } - if let Some(parent_label) = &self.parent_label { - parent_label.visit_params(visit); - } - for note in &self.notes { note.visit_params(visit); } + + if let Some(parent_label) = &self.parent_label { + parent_label.visit_params(visit); + } } } @@ -117,6 +131,12 @@ pub struct OnUnimplementedCondition { pub pred: Predicate, } +impl OnUnimplementedCondition { + pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { + self.pred.visit_params(self.span, visit); + } +} + /// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`]. /// /// It is similar to the predicate in the `cfg` attribute, @@ -145,6 +165,17 @@ impl Predicate { Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)), } } + + pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { + match self { + Predicate::Flag(_) => {} + Predicate::Match(nv) => nv.visit_params(span, visit), + Predicate::Not(not) => not.visit_params(span, visit), + Predicate::All(preds) | Predicate::Any(preds) => { + preds.iter().for_each(|pred| pred.visit_params(span, visit)) + } + } + } } /// Represents a `MetaWord` in an `on`-filter. @@ -172,6 +203,15 @@ pub struct NameValue { pub value: FilterFormatString, } +impl NameValue { + pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { + if let Name::GenericArg(arg) = self.name { + visit(arg, span); + } + self.value.visit_params(span, visit); + } +} + /// The valid names of the `on` filter. #[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Name { @@ -198,6 +238,16 @@ pub struct FilterFormatString { pub pieces: ThinVec, } +impl FilterFormatString { + pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { + for piece in &self.pieces { + if let LitOrArg::Arg(arg) = piece { + visit(*arg, span); + } + } + } +} + #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum LitOrArg { Lit(Symbol), diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 8e0ec9e489cf6..950a90c54e184 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -828,7 +828,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), tcx.ensure_ok().predicates_of(def_id); tcx.ensure_ok().associated_items(def_id); let assoc_items = tcx.associated_items(def_id); - check_diagnostic_attrs(tcx, def_id); for &assoc_item in assoc_items.in_definition_order() { match assoc_item.kind { diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 6bcbecce91493..a81b787ed5dd5 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3894,7 +3894,7 @@ pub(crate) struct WrappedParserError<'a> { #[derive(LintDiagnostic)] #[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")] pub(crate) struct IgnoredDiagnosticOption { - pub option_name: &'static str, + pub option_name: Symbol, #[label("`{$option_name}` is first declared here")] pub first_span: Span, #[label("`{$option_name}` is later redundantly declared here")] diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index b2664b4794b48..f445b30979b25 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -846,7 +846,7 @@ pub enum AttributeLintKind { span: Span, }, IgnoredDiagnosticOption { - option_name: &'static str, + option_name: Symbol, first_span: Span, later_span: Span, }, diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 34a2a519f4c4c..f136c04ed9d60 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -51,7 +51,6 @@ use rustc_session::parse::feature_err; use rustc_span::edition::Edition; use rustc_span::{BytePos, DUMMY_SP, Ident, Span, Symbol, sym}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_trait_selection::error_reporting::traits::on_unimplemented_format::errors::UnknownFormatParameterForOnUnimplementedAttr; use rustc_trait_selection::infer::{TyCtxtInferExt, ValuePairs}; use rustc_trait_selection::traits::ObligationCtxt; use tracing::debug; @@ -645,9 +644,10 @@ impl<'tcx> CheckAttrVisitor<'tcx> { MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, hir_id, span, - UnknownFormatParameterForOnUnimplementedAttr { + errors::UnknownFormatParameterForOnUnimplementedAttr { argument_name, trait_name: *trait_name, + help: !directive.is_rustc_attr, }, ) } diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 281279abd1e1b..18414a596a6bf 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -10,7 +10,7 @@ use rustc_hir::Target; use rustc_hir::attrs::{MirDialect, MirPhase}; use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic}; use rustc_middle::ty::{MainDefinition, Ty}; -use rustc_span::{DUMMY_SP, Span, Symbol}; +use rustc_span::{DUMMY_SP, Ident, Span, Symbol}; use crate::check_attr::ProcMacroKind; use crate::lang_items::Duplicate; @@ -1449,3 +1449,13 @@ pub(crate) struct FunctionNamesDuplicated { #[primary_span] pub spans: Vec, } + +#[derive(LintDiagnostic)] +#[diag("there is no parameter `{$argument_name}` on trait `{$trait_name}`")] +pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr { + pub argument_name: Symbol, + pub trait_name: Ident, + // `false` if we're in rustc_on_unimplemented, since its syntax is a lot more complex. + #[help("expect either a generic argument name or {\"`{Self}`\"} as format argument")] + pub help: bool, +} diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 945a189ab6227..03bc823fd180b 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -1,4 +1,3 @@ -use std::iter; use std::path::PathBuf; use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; @@ -17,7 +16,7 @@ use rustc_hir::{AttrArgs, Attribute, find_attr}; use rustc_macros::LintDiagnostic; use rustc_middle::bug; use rustc_middle::ty::print::PrintTraitRefExt; -use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKind, TyCtxt}; +use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind, TyCtxt}; use rustc_session::lint::builtin::{ MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, }; @@ -27,60 +26,11 @@ use tracing::{debug, info}; use super::{ObligationCauseCode, PredicateObligation}; use crate::error_reporting::TypeErrCtxt; -use crate::error_reporting::traits::on_unimplemented_condition::{ - matches_predicate, parse_condition, -}; +use crate::error_reporting::traits::on_unimplemented_condition::matches_predicate; use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs}; -use crate::errors::{InvalidOnClause, NoValueInOnUnimplemented}; -use crate::infer::InferCtxtExt; -impl<'tcx> TypeErrCtxt<'_, 'tcx> { - fn impl_similar_to( - &self, - trait_pred: ty::PolyTraitPredicate<'tcx>, - obligation: &PredicateObligation<'tcx>, - ) -> Option<(DefId, GenericArgsRef<'tcx>)> { - let tcx = self.tcx; - let param_env = obligation.param_env; - self.enter_forall(trait_pred, |trait_pred| { - let trait_self_ty = trait_pred.self_ty(); - - let mut self_match_impls = vec![]; - let mut fuzzy_match_impls = vec![]; - - self.tcx.for_each_relevant_impl(trait_pred.def_id(), trait_self_ty, |def_id| { - let impl_args = self.fresh_args_for_item(obligation.cause.span, def_id); - let impl_trait_ref = tcx.impl_trait_ref(def_id).instantiate(tcx, impl_args); - - let impl_self_ty = impl_trait_ref.self_ty(); - - if self.can_eq(param_env, trait_self_ty, impl_self_ty) { - self_match_impls.push((def_id, impl_args)); - - if iter::zip( - trait_pred.trait_ref.args.types().skip(1), - impl_trait_ref.args.types().skip(1), - ) - .all(|(u, v)| self.fuzzy_match_tys(u, v, false).is_some()) - { - fuzzy_match_impls.push((def_id, impl_args)); - } - } - }); - - let impl_def_id_and_args = if let [impl_] = self_match_impls[..] { - impl_ - } else if let [impl_] = fuzzy_match_impls[..] { - impl_ - } else { - return None; - }; - - #[allow(deprecated)] - tcx.has_attr(impl_def_id_and_args.0, sym::rustc_on_unimplemented) - .then_some(impl_def_id_and_args) - }) - } +use crate::errors::NoValueInOnUnimplemented; +impl<'tcx> TypeErrCtxt<'_, 'tcx> { /// Used to set on_unimplemented's `ItemContext` /// to be the enclosing (async) block/function/closure fn describe_enclosure(&self, def_id: LocalDefId) -> Option<&'static str> { @@ -130,9 +80,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, ) -> (ConditionOptions, FormatArgs<'tcx>) { - let (def_id, args) = self - .impl_similar_to(trait_pred, obligation) - .unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args)); + let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); let mut self_types = vec![]; @@ -384,41 +332,19 @@ fn parse_directive<'tcx>( tcx: TyCtxt<'tcx>, item_def_id: DefId, items: &[MetaItemInner], - span: Span, + _span: Span, is_root: bool, is_diagnostic_namespace_variant: bool, ) -> Result, ErrorGuaranteed> { let mut errored = None; - let mut item_iter = items.iter(); + let item_iter = items.iter(); let parse_value = |value_str, span| { try_parse_format_string(tcx, item_def_id, value_str, span, is_diagnostic_namespace_variant) .map(Some) }; - let condition = if is_root { - None - } else { - let cond = - item_iter.next().ok_or_else(|| tcx.dcx().emit_err(InvalidOnClause::Empty { span }))?; - - let generics: Vec = tcx - .generics_of(item_def_id) - .own_params - .iter() - .filter_map(|param| { - if matches!(param.kind, GenericParamDefKind::Lifetime) { - None - } else { - Some(param.name) - } - }) - .collect(); - match parse_condition(cond, &generics) { - Ok(condition) => Some(condition), - Err(e) => return Err(tcx.dcx().emit_err(e)), - } - }; + let condition = if is_root { None } else { unreachable!() }; let mut message = None; let mut label = None; @@ -522,6 +448,7 @@ fn parse_directive<'tcx>( if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) } } else { Ok(Some(OnUnimplementedDirective { + is_rustc_attr: !is_diagnostic_namespace_variant, condition, subcommands, message, @@ -548,13 +475,8 @@ pub fn of_item_directive<'tcx>( // We don't support those. return Ok(None); }; - if let Some(attr) = { - #[allow(deprecated)] - tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) - } { - return parse_attribute_directive(attr, false, tcx, item_def_id); - } else if attr == sym::on_unimplemented { - Ok(find_attr!(tcx.get_all_attrs(item_def_id), AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref().cloned()).flatten()) + if attr == sym::on_unimplemented { + Ok(find_attr!(tcx, item_def_id, AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref().cloned()).flatten()) } else { tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) .filter_map(|attr| parse_attribute_directive(attr, true, tcx, item_def_id).transpose()) @@ -606,6 +528,7 @@ pub fn of_item_directive<'tcx>( ); Ok(Some(OnUnimplementedDirective { + is_rustc_attr: false, condition: aggr.condition.or(directive.condition), subcommands, message: aggr.message.or(directive.message), @@ -639,6 +562,7 @@ fn parse_attribute_directive<'tcx>( } else if let Some(value) = attr.value_str() { if !is_diagnostic_namespace_variant { Ok(Some(OnUnimplementedDirective { + is_rustc_attr: !is_diagnostic_namespace_variant, condition: None, message: None, subcommands: thin_vec![], diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs index b18642c785d94..c92b8dbc0fea2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs @@ -1,10 +1,5 @@ -use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit}; use rustc_hir::attrs::diagnostic::*; -use rustc_parse_format::{ParseMode, Parser, Piece, Position}; -use rustc_span::{Ident, Symbol, kw, sym}; -use thin_vec::ThinVec; - -use crate::errors::InvalidOnClause; +use rustc_span::Symbol; pub fn matches_predicate(slf: &OnUnimplementedCondition, options: &ConditionOptions) -> bool { slf.pred.eval(&mut |p| match p { @@ -16,131 +11,18 @@ pub fn matches_predicate(slf: &OnUnimplementedCondition, options: &ConditionOpti }) } -pub(crate) fn parse_condition( - input: &MetaItemInner, - generics: &[Symbol], -) -> Result { - let span = input.span(); - let pred = parse_predicate(input, generics)?; - Ok(OnUnimplementedCondition { span, pred }) -} - -fn parse_predicate( - input: &MetaItemInner, - generics: &[Symbol], -) -> Result { - let meta_item = match input { - MetaItemInner::MetaItem(meta_item) => meta_item, - MetaItemInner::Lit(lit) => { - return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span }); - } - }; - - let Some(predicate) = meta_item.ident() else { - return Err(InvalidOnClause::ExpectedIdentifier { - span: meta_item.path.span, - path: meta_item.path.clone(), - }); - }; - - match meta_item.kind { - MetaItemKind::List(ref mis) => match predicate.name { - sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis, generics)?)), - sym::all => Ok(Predicate::All(parse_predicate_sequence(mis, generics)?)), - sym::not => match &**mis { - [one] => Ok(Predicate::Not(Box::new(parse_predicate(one, generics)?))), - [first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot { - span: first.span().to(last.span()), - }), - [] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }), - }, - invalid_pred => { - Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred }) - } - }, - MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => { - let name = parse_name(predicate, generics)?; - let value = parse_filter(symbol); - let kv = NameValue { name, value }; - Ok(Predicate::Match(kv)) - } - MetaItemKind::Word => { - let flag = parse_flag(predicate)?; - Ok(Predicate::Flag(flag)) - } - } -} - -fn parse_predicate_sequence( - sequence: &[MetaItemInner], - generics: &[Symbol], -) -> Result, InvalidOnClause> { - sequence.iter().map(|item| parse_predicate(item, generics)).collect() -} - -fn parse_flag(Ident { name, span }: Ident) -> Result { - match name { - sym::crate_local => Ok(Flag::CrateLocal), - sym::direct => Ok(Flag::Direct), - sym::from_desugaring => Ok(Flag::FromDesugaring), - invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }), - } -} - -fn parse_name(Ident { name, span }: Ident, generics: &[Symbol]) -> Result { - match name { - kw::SelfUpper => Ok(Name::SelfUpper), - sym::from_desugaring => Ok(Name::FromDesugaring), - sym::cause => Ok(Name::Cause), - generic if generics.contains(&generic) => Ok(Name::GenericArg(generic)), - invalid_name => Err(InvalidOnClause::InvalidName { invalid_name, span }), - } -} - -fn parse_filter(input: Symbol) -> FilterFormatString { - let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic) - .map(|p| match p { - Piece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)), - // We just ignore formatspecs here - Piece::NextArgument(a) => match a.position { - // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even - // if the integer type has been resolved, to allow targeting all integers. - // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet, - // from the `Display` impl of `InferTy` to be precise. - // - // Don't try to format these later! - Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { - LitOrArg::Lit(Symbol::intern(&format!("{{{arg}}}"))) - } - - // FIXME(mejrs) We should check if these correspond to a generic of the trait. - Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)), - - // FIXME(mejrs) These should really be warnings/errors - Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces), - Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&format!("{{{idx}}}"))), - }, - }) - .collect(); - FilterFormatString { pieces } -} - fn format_filter(slf: &FilterFormatString, generic_args: &[(Symbol, String)]) -> String { let mut ret = String::new(); for piece in &slf.pieces { match piece { LitOrArg::Lit(s) => ret.push_str(s.as_str()), - LitOrArg::Arg(s) => { - match generic_args.iter().find(|(k, _)| k == s) { - Some((_, val)) => ret.push_str(val), - None => { - // FIXME(mejrs) If we start checking as mentioned in - // FilterFormatString::parse then this shouldn't happen - let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); - } + LitOrArg::Arg(s) => match generic_args.iter().find(|(k, _)| k == s) { + Some((_, val)) => ret.push_str(val), + None => { + let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); } - } + }, } } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 737778d917543..376a5a6098c37 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -227,17 +227,6 @@ fn slice_span(input: Span, Range { start, end }: Range, is_source_literal pub mod errors { use rustc_macros::LintDiagnostic; - use rustc_span::Ident; - - use super::*; - - #[derive(LintDiagnostic)] - #[diag("there is no parameter `{$argument_name}` on trait `{$trait_name}`")] - #[help("expect either a generic argument name or {\"`{Self}`\"} as format argument")] - pub struct UnknownFormatParameterForOnUnimplementedAttr { - pub argument_name: Symbol, - pub trait_name: Ident, - } #[derive(LintDiagnostic)] #[diag("positional format arguments are not allowed here")] diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs index 23234c2080690..2bb9816a31005 100644 --- a/compiler/rustc_trait_selection/src/errors.rs +++ b/compiler/rustc_trait_selection/src/errors.rs @@ -1,4 +1,3 @@ -use rustc_ast::Path; use rustc_data_structures::fx::{FxHashSet, FxIndexSet}; use rustc_errors::codes::*; use rustc_errors::{ @@ -28,60 +27,6 @@ pub struct UnableToConstructConstantValue<'a> { pub unevaluated: ty::UnevaluatedConst<'a>, } -#[derive(Diagnostic)] -pub enum InvalidOnClause { - #[diag("empty `on`-clause in `#[rustc_on_unimplemented]`", code = E0232)] - Empty { - #[primary_span] - #[label("empty `on`-clause here")] - span: Span, - }, - #[diag("expected a single predicate in `not(..)`", code = E0232)] - ExpectedOnePredInNot { - #[primary_span] - #[label("unexpected quantity of predicates here")] - span: Span, - }, - #[diag("literals inside `on`-clauses are not supported", code = E0232)] - UnsupportedLiteral { - #[primary_span] - #[label("unexpected literal here")] - span: Span, - }, - #[diag("expected an identifier inside this `on`-clause", code = E0232)] - ExpectedIdentifier { - #[primary_span] - #[label("expected an identifier here, not `{$path}`")] - span: Span, - path: Path, - }, - #[diag("this predicate is invalid", code = E0232)] - InvalidPredicate { - #[primary_span] - #[label("expected one of `any`, `all` or `not` here, not `{$invalid_pred}`")] - span: Span, - invalid_pred: Symbol, - }, - #[diag("invalid flag in `on`-clause", code = E0232)] - InvalidFlag { - #[primary_span] - #[label( - "expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`" - )] - span: Span, - invalid_flag: Symbol, - }, - #[diag("invalid name in `on`-clause", code = E0232)] - InvalidName { - #[primary_span] - #[label( - "expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `{$invalid_name}`" - )] - span: Span, - invalid_name: Symbol, - }, -} - #[derive(Diagnostic)] #[diag("this attribute must have a value", code = E0232)] #[note("e.g. `#[rustc_on_unimplemented(message=\"foo\")]`")] diff --git a/tests/incremental/issue-59523-on-implemented-is-not-unused.rs b/tests/incremental/issue-59523-on-implemented-is-not-unused.rs index 8260bd350e721..59e972857f377 100644 --- a/tests/incremental/issue-59523-on-implemented-is-not-unused.rs +++ b/tests/incremental/issue-59523-on-implemented-is-not-unused.rs @@ -9,13 +9,12 @@ #![feature(rustc_attrs)] #![deny(unused_attributes)] -#[rustc_on_unimplemented = "invalid"] +#[rustc_on_unimplemented(label ="invalid")] trait Index { type Output: ?Sized; fn index(&self, index: Idx) -> &Self::Output; } -#[rustc_on_unimplemented = "a usize is required to index into a slice"] impl Index for [i32] { type Output = i32; fn index(&self, index: usize) -> &i32 { diff --git a/tests/ui/on-unimplemented/bad-annotation.rs b/tests/ui/on-unimplemented/bad-annotation.rs index 937e5b82da50c..c8d846a2273df 100644 --- a/tests/ui/on-unimplemented/bad-annotation.rs +++ b/tests/ui/on-unimplemented/bad-annotation.rs @@ -2,26 +2,28 @@ #![feature(rustc_attrs)] #![allow(unused)] -#[rustc_on_unimplemented = "test error `{Self}` with `{Bar}` `{Baz}` `{Quux}`"] +#[rustc_on_unimplemented(label = "test error `{Self}` with `{Bar}` `{Baz}` `{Quux}`")] trait Foo {} -#[rustc_on_unimplemented = "a collection of type `{Self}` cannot \ - be built from an iterator over elements of type `{A}`"] +#[rustc_on_unimplemented(label = "a collection of type `{Self}` cannot \ + be built from an iterator over elements of type `{A}`")] trait MyFromIterator { /// Builds a container with elements from an external iterator. fn my_from_iter>(iterator: T) -> Self; } #[rustc_on_unimplemented] -//~^ ERROR malformed `rustc_on_unimplemented` attribute +//~^ WARN missing options for `on_unimplemented` attribute +//~| NOTE part of trait NoContent {} -#[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"] -//~^ ERROR cannot find parameter C on this trait +#[rustc_on_unimplemented(label = "Unimplemented error on `{Self}` with params `<{A},{B},{C}>`")] +//~^ WARN there is no parameter `C` on trait `ParameterNotPresent` +//~| NOTE part of trait ParameterNotPresent {} -#[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"] -//~^ ERROR positional format arguments are not allowed here +#[rustc_on_unimplemented(label = "Unimplemented error on `{Self}` with params `<{A},{B},{}>`")] +//~^ WARN positional format arguments are not allowed here trait NoPositionalArgs {} #[rustc_on_unimplemented(lorem = "")] @@ -43,9 +45,8 @@ trait Invalid {} trait DuplicateMessage {} #[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))] -//~^ ERROR this attribute must have a value -//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]` -//~^^^ NOTE expected value here +//~^ ERROR invalid flag in `on`-clause +//~| NOTE expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `desugared` trait OnInWrongPosition {} #[rustc_on_unimplemented(on(), message = "y")] @@ -60,6 +61,9 @@ trait EmptyOn {} trait ExpectedPredicateInOn {} #[rustc_on_unimplemented(on(Self = "y"), message = "y")] +//~^ ERROR this attribute must have a value +//~| NOTE expected value here +//~| NOTE e.g. `#[rustc_on_unimplemented(message="foo")]` trait OnWithoutDirectives {} #[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")] @@ -109,11 +113,9 @@ trait InvalidPredicate {} trait InvalidFlag {} #[rustc_on_unimplemented(on(_Self = "y", message = "y"))] -//~^ ERROR invalid name in `on`-clause -//~^^ NOTE expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `_Self` +//~^ WARN there is no parameter `_Self` on trait `InvalidName` trait InvalidName {} #[rustc_on_unimplemented(on(abc = "y", message = "y"))] -//~^ ERROR invalid name in `on`-clause -//~^^ NOTE expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `abc` +//~^ WARN there is no parameter `abc` on trait `InvalidName2` trait InvalidName2 {} diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr index 1777dc849d0c1..6316dd6aa2d27 100644 --- a/tests/ui/on-unimplemented/bad-annotation.stderr +++ b/tests/ui/on-unimplemented/bad-annotation.stderr @@ -1,24 +1,5 @@ -error: malformed `rustc_on_unimplemented` attribute input - --> $DIR/bad-annotation.rs:15:1 - | -LL | #[rustc_on_unimplemented] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | -help: the following are the possible correct uses - | -LL | #[rustc_on_unimplemented = "message"] - | +++++++++++ -LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...")] - | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -error[E0231]: positional format arguments are not allowed here - --> $DIR/bad-annotation.rs:23:90 - | -LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"] - | ^ - error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:27:26 + --> $DIR/bad-annotation.rs:29:26 | LL | #[rustc_on_unimplemented(lorem = "")] | ^^^^^^^^^^ expected value here @@ -26,7 +7,7 @@ LL | #[rustc_on_unimplemented(lorem = "")] = note: e.g. `#[rustc_on_unimplemented(message="foo")]` error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:33:26 + --> $DIR/bad-annotation.rs:35:26 | LL | #[rustc_on_unimplemented(lorem(ipsum(dolor)))] | ^^^^^^^^^^^^^^^^^^^ expected value here @@ -34,29 +15,27 @@ LL | #[rustc_on_unimplemented(lorem(ipsum(dolor)))] = note: e.g. `#[rustc_on_unimplemented(message="foo")]` error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:39:41 + --> $DIR/bad-annotation.rs:41:41 | LL | #[rustc_on_unimplemented(message = "x", message = "y")] | ^^^^^^^^^^^^^ expected value here | = note: e.g. `#[rustc_on_unimplemented(message="foo")]` -error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:45:41 +error[E0232]: invalid flag in `on`-clause + --> $DIR/bad-annotation.rs:47:44 | LL | #[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here - | - = note: e.g. `#[rustc_on_unimplemented(message="foo")]` + | ^^^^^^^^^ expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `desugared` error[E0232]: empty `on`-clause in `#[rustc_on_unimplemented]` - --> $DIR/bad-annotation.rs:51:26 + --> $DIR/bad-annotation.rs:52:26 | LL | #[rustc_on_unimplemented(on(), message = "y")] | ^^^^ empty `on`-clause here error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:56:26 + --> $DIR/bad-annotation.rs:57:26 | LL | #[rustc_on_unimplemented(on = "x", message = "y")] | ^^^^^^^^ expected value here @@ -64,7 +43,15 @@ LL | #[rustc_on_unimplemented(on = "x", message = "y")] = note: e.g. `#[rustc_on_unimplemented(message="foo")]` error[E0232]: this attribute must have a value - --> $DIR/bad-annotation.rs:65:46 + --> $DIR/bad-annotation.rs:63:26 + | +LL | #[rustc_on_unimplemented(on(Self = "y"), message = "y")] + | ^^^^^^^^^^^^^^ expected value here + | + = note: e.g. `#[rustc_on_unimplemented(message="foo")]` + +error[E0232]: this attribute must have a value + --> $DIR/bad-annotation.rs:69:46 | LL | #[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here @@ -72,66 +59,90 @@ LL | #[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = = note: e.g. `#[rustc_on_unimplemented(message="foo")]` error[E0232]: literals inside `on`-clauses are not supported - --> $DIR/bad-annotation.rs:71:29 + --> $DIR/bad-annotation.rs:75:29 | LL | #[rustc_on_unimplemented(on("y", message = "y"))] | ^^^ unexpected literal here error[E0232]: literals inside `on`-clauses are not supported - --> $DIR/bad-annotation.rs:76:29 + --> $DIR/bad-annotation.rs:80:29 | LL | #[rustc_on_unimplemented(on(42, message = "y"))] | ^^ unexpected literal here error[E0232]: expected a single predicate in `not(..)` - --> $DIR/bad-annotation.rs:81:33 + --> $DIR/bad-annotation.rs:85:32 | LL | #[rustc_on_unimplemented(on(not(a, b), message = "y"))] - | ^^^^ unexpected quantity of predicates here + | ^^^^^^ unexpected quantity of predicates here error[E0232]: expected a single predicate in `not(..)` - --> $DIR/bad-annotation.rs:86:29 + --> $DIR/bad-annotation.rs:90:32 | LL | #[rustc_on_unimplemented(on(not(), message = "y"))] - | ^^^^^ unexpected quantity of predicates here + | ^^ unexpected quantity of predicates here error[E0232]: expected an identifier inside this `on`-clause - --> $DIR/bad-annotation.rs:91:29 + --> $DIR/bad-annotation.rs:95:29 | LL | #[rustc_on_unimplemented(on(thing::What, message = "y"))] | ^^^^^^^^^^^ expected an identifier here, not `thing::What` error[E0232]: expected an identifier inside this `on`-clause - --> $DIR/bad-annotation.rs:96:29 + --> $DIR/bad-annotation.rs:100:29 | LL | #[rustc_on_unimplemented(on(thing::What = "value", message = "y"))] | ^^^^^^^^^^^ expected an identifier here, not `thing::What` error[E0232]: this predicate is invalid - --> $DIR/bad-annotation.rs:101:29 + --> $DIR/bad-annotation.rs:105:29 | LL | #[rustc_on_unimplemented(on(aaaaaaaaaaaaaa(a, b), message = "y"))] | ^^^^^^^^^^^^^^ expected one of `any`, `all` or `not` here, not `aaaaaaaaaaaaaa` error[E0232]: invalid flag in `on`-clause - --> $DIR/bad-annotation.rs:106:29 + --> $DIR/bad-annotation.rs:110:29 | LL | #[rustc_on_unimplemented(on(something, message = "y"))] | ^^^^^^^^^ expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `something` -error[E0232]: invalid name in `on`-clause - --> $DIR/bad-annotation.rs:111:29 +warning: there is no parameter `C` on trait `ParameterNotPresent` + --> $DIR/bad-annotation.rs:20:90 + | +LL | #[rustc_on_unimplemented(label = "Unimplemented error on `{Self}` with params `<{A},{B},{C}>`")] + | ^ + | + = note: `#[warn(malformed_diagnostic_format_literals)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: there is no parameter `_Self` on trait `InvalidName` + --> $DIR/bad-annotation.rs:115:29 | LL | #[rustc_on_unimplemented(on(_Self = "y", message = "y"))] - | ^^^^^ expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `_Self` + | ^^^^^^^^^^^ -error[E0232]: invalid name in `on`-clause - --> $DIR/bad-annotation.rs:116:29 +warning: there is no parameter `abc` on trait `InvalidName2` + --> $DIR/bad-annotation.rs:119:29 | LL | #[rustc_on_unimplemented(on(abc = "y", message = "y"))] - | ^^^ expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `abc` + | ^^^^^^^^^ + +warning: missing options for `on_unimplemented` attribute + --> $DIR/bad-annotation.rs:15:1 + | +LL | #[rustc_on_unimplemented] + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: at least one of the `message`, `note` and `label` options are expected + = note: `#[warn(malformed_diagnostic_attributes)]` (part of `#[warn(unknown_or_malformed_diagnostic_attributes)]`) on by default + +warning: positional format arguments are not allowed here + --> $DIR/bad-annotation.rs:25:90 + | +LL | #[rustc_on_unimplemented(label = "Unimplemented error on `{Self}` with params `<{A},{B},{}>`")] + | ^ + | + = help: only named format arguments with the name of one of the generic types are allowed in this context -error: aborting due to 19 previous errors +error: aborting due to 16 previous errors; 5 warnings emitted -Some errors have detailed explanations: E0231, E0232. -For more information about an error, try `rustc --explain E0231`. +For more information about this error, try `rustc --explain E0232`. diff --git a/tests/ui/on-unimplemented/feature-gate-on-unimplemented.rs b/tests/ui/on-unimplemented/feature-gate-on-unimplemented.rs index 436caab5d64aa..1fdb1d2f2c0cb 100644 --- a/tests/ui/on-unimplemented/feature-gate-on-unimplemented.rs +++ b/tests/ui/on-unimplemented/feature-gate-on-unimplemented.rs @@ -1,6 +1,6 @@ // Test that `#[rustc_on_unimplemented]` is gated by `rustc_attrs` feature gate. -#[rustc_on_unimplemented = "test error `{Self}` with `{Bar}`"] +#[rustc_on_unimplemented(label = "test error `{Self}` with `{Bar}`")] //~^ ERROR use of an internal attribute [E0658] //~| NOTE the `#[rustc_on_unimplemented]` attribute is an internal implementation detail that will never be stable //~| NOTE see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute diff --git a/tests/ui/on-unimplemented/feature-gate-on-unimplemented.stderr b/tests/ui/on-unimplemented/feature-gate-on-unimplemented.stderr index d1983088af853..c6680666ece05 100644 --- a/tests/ui/on-unimplemented/feature-gate-on-unimplemented.stderr +++ b/tests/ui/on-unimplemented/feature-gate-on-unimplemented.stderr @@ -1,8 +1,8 @@ error[E0658]: use of an internal attribute --> $DIR/feature-gate-on-unimplemented.rs:3:1 | -LL | #[rustc_on_unimplemented = "test error `{Self}` with `{Bar}`"] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | #[rustc_on_unimplemented(label = "test error `{Self}` with `{Bar}`")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: add `#![feature(rustc_attrs)]` to the crate attributes to enable = note: the `#[rustc_on_unimplemented]` attribute is an internal implementation detail that will never be stable diff --git a/tests/ui/on-unimplemented/on-trait.rs b/tests/ui/on-unimplemented/on-trait.rs index 91630af17e92a..0639d36eff409 100644 --- a/tests/ui/on-unimplemented/on-trait.rs +++ b/tests/ui/on-unimplemented/on-trait.rs @@ -3,7 +3,7 @@ #![feature(rustc_attrs)] pub mod Bar { - #[rustc_on_unimplemented = "test error `{Self}` with `{Bar}` `{Baz}` `{Quux}` in `{This}`"] + #[rustc_on_unimplemented(label ="test error `{Self}` with `{Bar}` `{Baz}` `{Quux}` in `{This}`")] pub trait Foo {} } @@ -13,7 +13,7 @@ fn foobar>() -> T { panic!() } -#[rustc_on_unimplemented="a collection of type `{Self}` cannot be built from an iterator over elements of type `{A}`"] +#[rustc_on_unimplemented(label ="a collection of type `{Self}` cannot be built from an iterator over elements of type `{A}`")] trait MyFromIterator { /// Builds a container with elements from an external iterator. fn my_from_iter>(iterator: T) -> Self; diff --git a/tests/ui/unpretty/diagnostic-attr.stdout b/tests/ui/unpretty/diagnostic-attr.stdout index d1bdb90ee9c1f..2258029f7e13b 100644 --- a/tests/ui/unpretty/diagnostic-attr.stdout +++ b/tests/ui/unpretty/diagnostic-attr.stdout @@ -5,7 +5,8 @@ use ::std::prelude::rust_2015::*; //@ check-pass //@ edition: 2015 -#[attr = OnUnimplemented {directive: OnUnimplementedDirective {subcommands: [], +#[attr = OnUnimplemented {directive: OnUnimplementedDirective {is_rustc_attr: false, +subcommands: [], message: FormatString {input: "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", pieces: [Lit("My Message for `ImportantTrait<"), Arg(GenericParam {generic_param: "A"}), Lit(">` implemented for `"), From 20e64466b3d0bf0e7e22970b8a965d01d2ea885a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:42:18 +0100 Subject: [PATCH 14/32] Port diagnostic::on_const --- .../src/attributes/diagnostic/mod.rs | 304 ++++++++++++++++-- .../src/attributes/diagnostic/on_const.rs | 57 ++++ .../attributes/diagnostic/on_unimplemented.rs | 259 +-------------- compiler/rustc_attr_parsing/src/context.rs | 2 + .../rustc_hir/src/attrs/data_structures.rs | 9 +- compiler/rustc_hir/src/attrs/diagnostic.rs | 6 +- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 + compiler/rustc_lint/src/early/diagnostics.rs | 6 + compiler/rustc_lint/src/lints.rs | 13 + compiler/rustc_lint_defs/src/lib.rs | 4 + compiler/rustc_passes/src/check_attr.rs | 11 +- .../traits/on_unimplemented.rs | 21 +- .../on_const/auxiliary/const_trait.rs | 10 + .../on_const/misplaced_attr.rs | 2 +- tests/ui/unpretty/diagnostic-attr.rs | 11 + tests/ui/unpretty/diagnostic-attr.stdout | 20 +- 16 files changed, 426 insertions(+), 310 deletions(-) create mode 100644 compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs create mode 100644 tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index d6fce0d1f0567..802d0148994b1 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -1,30 +1,286 @@ -#![allow(warnings)] use std::ops::Range; use rustc_errors::E0232; use rustc_hir::AttrPath; use rustc_hir::attrs::diagnostic::{ - FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue, - OnUnimplementedCondition, Piece, Predicate, + AppendConstMessage, Directive, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, + Name, NameValue, OnUnimplementedCondition, Piece, Predicate, }; -use rustc_hir::lints::FormatWarning; +use rustc_hir::lints::{AttributeLintKind, FormatWarning}; use rustc_macros::Diagnostic; use rustc_parse_format::{ Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, }; +use rustc_session::lint::builtin::{ + MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, +}; use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym}; -use thin_vec::ThinVec; +use thin_vec::{ThinVec, thin_vec}; +use crate::context::{AcceptContext, Stage}; use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser}; -pub mod on_unimplemented; +pub(crate) mod on_const; +pub(crate) mod on_unimplemented; #[derive(Copy, Clone)] pub(crate) enum Mode { - // `#[rustc_on_unimplemented]` + /// `#[rustc_on_unimplemented]` RustcOnUnimplemented, - // `#[diagnostic::...]` + /// `#[diagnostic::on_unimplemented]` DiagnosticOnUnimplemented, + /// `#[diagnostic::on_const]` + DiagnosticOnConst, +} + +fn merge_directives( + cx: &mut AcceptContext<'_, '_, S>, + first: &mut Option<(Span, Directive)>, + later: (Span, Directive), +) { + if let Some((_, first)) = first { + if first.is_rustc_attr || later.1.is_rustc_attr { + cx.emit_err(DupesNotAllowed); + } + + merge(cx, &mut first.message, later.1.message, sym::message); + merge(cx, &mut first.label, later.1.label, sym::label); + first.notes.extend(later.1.notes); + } else { + *first = Some(later); + } +} + +fn merge( + cx: &mut AcceptContext<'_, '_, S>, + first: &mut Option<(Span, T)>, + later: Option<(Span, T)>, + option_name: Symbol, +) { + match (first, later) { + (Some(_) | None, None) => {} + (Some((first_span, _)), Some((later_span, _))) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::IgnoredDiagnosticOption { + first_span: *first_span, + later_span, + option_name, + }, + later_span, + ); + } + (first @ None, Some(later)) => { + first.get_or_insert(later); + } + } +} + +fn parse_directive_items<'p, S: Stage>( + cx: &mut AcceptContext<'_, '_, S>, + mode: Mode, + items: impl Iterator, + is_root: bool, +) -> Option { + let condition = None; + let mut message: Option<(Span, _)> = None; + let mut label: Option<(Span, _)> = None; + let mut notes = ThinVec::new(); + let mut parent_label = None; + let mut subcommands = ThinVec::new(); + let mut append_const_msg = None; + + for item in items { + let span = item.span(); + + macro malformed() {{ + match mode { + Mode::RustcOnUnimplemented => { + cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); + } + Mode::DiagnosticOnUnimplemented => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnUnimplementedAttr { span }, + span, + ); + } + Mode::DiagnosticOnConst => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnConstAttr { span }, + span, + ); + } + } + continue; + }} + + macro or_malformed($($code:tt)*) {{ + let Some(ret) = (try { + $($code)* + }) else { + + malformed!() + }; + ret + }} + + macro duplicate($name: ident, $($first_span:tt)*) {{ + match mode { + Mode::RustcOnUnimplemented => { + cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); + } + Mode::DiagnosticOnUnimplemented |Mode::DiagnosticOnConst => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::IgnoredDiagnosticOption { + first_span: $($first_span)*, + later_span: span, + option_name: $name, + }, + span, + ); + } + } + }} + + let item: &MetaItemParser = or_malformed!(item.meta_item()?); + let name = or_malformed!(item.ident()?).name; + + // Some things like `message = "message"` must have a value. + // But with things like `append_const_msg` that is optional. + let value: Option = match item.args().name_value() { + Some(nv) => Some(or_malformed!(nv.value_as_ident()?)), + None => None, + }; + + let mut parse_format = |input: Ident| { + let snippet = cx.sess.source_map().span_to_snippet(input.span).ok(); + let is_snippet = snippet.is_some(); + match parse_format_string(input.name, snippet, input.span, mode) { + Ok((f, warnings)) => { + for warning in warnings { + let (FormatWarning::InvalidSpecifier { span, .. } + | FormatWarning::PositionalArgument { span, .. }) = warning; + cx.emit_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + AttributeLintKind::MalformedDiagnosticFormat { warning }, + span, + ); + } + + f + } + Err(e) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, + AttributeLintKind::DiagnosticWrappedParserError { + description: e.description, + label: e.label, + span: slice_span(input.span, e.span, is_snippet), + }, + input.span, + ); + // We could not parse the input, just use it as-is. + FormatString { + input: input.name, + span: input.span, + pieces: thin_vec![Piece::Lit(input.name)], + } + } + } + }; + match (mode, name) { + (_, sym::message) => { + let value = or_malformed!(value?); + if let Some(message) = &message { + duplicate!(name, message.0) + } else { + message = Some((item.span(), parse_format(value))); + } + } + (_, sym::label) => { + let value = or_malformed!(value?); + if let Some(label) = &label { + duplicate!(name, label.0) + } else { + label = Some((item.span(), parse_format(value))); + } + } + (_, sym::note) => { + let value = or_malformed!(value?); + notes.push(parse_format(value)) + } + + (Mode::RustcOnUnimplemented, sym::append_const_msg) => { + append_const_msg = if let Some(msg) = value { + Some(AppendConstMessage::Custom(msg.name, item.span())) + } else { + Some(AppendConstMessage::Default) + } + } + (Mode::RustcOnUnimplemented, sym::parent_label) => { + let value = or_malformed!(value?); + if parent_label.is_none() { + parent_label = Some(parse_format(value)); + } else { + // warn + } + } + (Mode::RustcOnUnimplemented, sym::on) => { + if is_root { + let items = or_malformed!(item.args().list()?); + let mut iter = items.mixed(); + let condition: &MetaItemOrLitParser = match iter.next() { + Some(c) => c, + None => { + cx.emit_err(InvalidOnClause::Empty { span }); + continue; + } + }; + + let condition = parse_condition(condition); + + if items.len() < 2 { + // Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]` + // There's a condition but no directive behind it, this is a mistake. + malformed!(); + } + + let mut directive = + or_malformed!(parse_directive_items(cx, mode, iter, false)?); + + match condition { + Ok(c) => { + directive.condition = Some(c); + subcommands.push(directive); + } + Err(e) => { + cx.emit_err(e); + } + } + } else { + malformed!(); + } + } + + _other => { + malformed!(); + } + } + } + + Some(Directive { + is_rustc_attr: matches!(mode, Mode::RustcOnUnimplemented), + condition, + subcommands, + message, + label, + notes, + parent_label, + append_const_msg, + }) } pub(crate) fn parse_format_string( @@ -74,14 +330,8 @@ fn parse_arg( (Mode::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, (Mode::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, // Any attribute can use these - ( - Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. }, - kw::SelfUpper, - ) => FormatArg::SelfUpper, - ( - Mode::RustcOnUnimplemented { .. } | Mode::DiagnosticOnUnimplemented { .. }, - generic_param, - ) => FormatArg::GenericParam { generic_param, span }, + (_, kw::SelfUpper) => FormatArg::SelfUpper, + (_, generic_param) => FormatArg::GenericParam { generic_param, span }, }, // `{:1}` and `{}` are ignored @@ -163,7 +413,7 @@ fn parse_predicate(input: &MetaItemOrLitParser) -> Result Result { } } -fn parse_name(Ident { name, span }: Ident) -> Name { +fn parse_name(name: Symbol) -> Name { match name { kw::SelfUpper => Name::SelfUpper, sym::from_desugaring => Name::FromDesugaring, @@ -211,14 +461,11 @@ fn parse_filter(input: Symbol) -> FilterFormatString { // from the `Display` impl of `InferTy` to be precise. // // Don't try to format these later! - Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => { + Position::ArgumentNamed(arg @ ("integer" | "integral" | "float")) => { LitOrArg::Lit(Symbol::intern(&format!("{{{arg}}}"))) } - // FIXME(mejrs) We should check if these correspond to a generic of the trait. Position::ArgumentNamed(arg) => LitOrArg::Arg(Symbol::intern(arg)), - - // FIXME(mejrs) These should really be warnings/errors Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(sym::empty_braces), Position::ArgumentIs(idx) => LitOrArg::Lit(Symbol::intern(&format!("{{{idx}}}"))), }, @@ -270,21 +517,12 @@ pub(crate) enum InvalidOnClause { span: Span, invalid_flag: Symbol, }, - #[diag("invalid name in `on`-clause", code = E0232)] - InvalidName { - #[primary_span] - #[label( - "expected one of `cause`, `from_desugaring`, `Self` or any generic parameter of the trait, not `{$invalid_name}`" - )] - span: Span, - invalid_name: Symbol, - }, } #[derive(Diagnostic)] #[diag("this attribute must have a value", code = E0232)] #[note("e.g. `#[rustc_on_unimplemented(message=\"foo\")]`")] -pub struct NoValueInOnUnimplemented { +pub(crate) struct NoValueInOnUnimplemented { #[primary_span] #[label("expected value here")] pub span: Span, @@ -294,4 +532,4 @@ pub struct NoValueInOnUnimplemented { #[diag( "using multiple `rustc_on_unimplemented` (or mixing it with `diagnostic::on_unimplemented`) is not supported" )] -pub struct DupesNotAllowed; +pub(crate) struct DupesNotAllowed; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs new file mode 100644 index 0000000000000..1b694b4611b50 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -0,0 +1,57 @@ +use rustc_hir::attrs::diagnostic::Directive; +use rustc_hir::lints::AttributeLintKind; +use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; + +use crate::attributes::diagnostic::*; +use crate::attributes::prelude::*; +use crate::attributes::template; + +#[derive(Default)] +pub(crate) struct OnConstParser { + directive: Option<(Span, Directive)>, +} + +impl AttributeParser for OnConstParser { + const ATTRIBUTES: AcceptMapping = + &[(&[sym::diagnostic, sym::on_const], template!(Word), |this, cx, args| { + if !cx.features().diagnostic_on_const() { + return; + } + let span = cx.attr_span; + + let items = match args { + ArgParser::List(items) if items.len() != 0 => items, + ArgParser::NoArgs | ArgParser::List(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MissingOptionsForOnConst, + span, + ); + return; + } + ArgParser::NameValue(_) => { + cx.emit_lint( + MALFORMED_DIAGNOSTIC_ATTRIBUTES, + AttributeLintKind::MalformedOnConstAttr { span }, + span, + ); + return; + } + }; + + let Some(directive) = + parse_directive_items(cx, Mode::DiagnosticOnConst, items.mixed(), true) + else { + return; + }; + merge_directives(cx, &mut this.directive, (span, directive)); + })]; + const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); + + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { + self.directive.map(|(span, directive)| AttributeKind::OnConst { + span, + directive: Some(Box::new(directive)), + }) + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 21db29aef15aa..13ef09051fd7c 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -1,18 +1,14 @@ -use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedDirective}; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::lints::AttributeLintKind; -use rustc_session::lint::builtin::{ - MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, -}; -use thin_vec::thin_vec; +use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; use crate::attributes::template; -/// Folds all uses of `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`. -/// FIXME(mejrs): add an example + #[derive(Default)] -pub struct OnUnimplementedParser { - directive: Option<(Span, OnUnimplementedDirective)>, +pub(crate) struct OnUnimplementedParser { + directive: Option<(Span, Directive)>, } impl OnUnimplementedParser { @@ -66,253 +62,10 @@ impl AttributeParser for OnUnimplementedParser { ]; const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); - fn finalize(mut self, cx: &FinalizeContext<'_, '_, S>) -> Option { + fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { self.directive.map(|(span, directive)| AttributeKind::OnUnimplemented { span, directive: Some(Box::new(directive)), }) } } - -fn merge_directives( - cx: &mut AcceptContext<'_, '_, S>, - first: &mut Option<(Span, OnUnimplementedDirective)>, - later: (Span, OnUnimplementedDirective), -) { - if let Some((first_span, first)) = first { - if first.is_rustc_attr || later.1.is_rustc_attr { - cx.emit_err(DupesNotAllowed); - } - - merge(cx, &mut first.message, later.1.message, sym::message); - merge(cx, &mut first.label, later.1.label, sym::label); - first.notes.extend(later.1.notes); - } else { - *first = Some(later); - } -} - -fn merge( - cx: &mut AcceptContext<'_, '_, S>, - first: &mut Option<(Span, T)>, - later: Option<(Span, T)>, - option_name: Symbol, -) { - match (first, later) { - (Some(_) | None, None) => {} - (Some((first_span, _)), Some((later_span, _))) => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::IgnoredDiagnosticOption { - first_span: *first_span, - later_span, - option_name, - }, - later_span, - ); - } - (first @ None, Some(later)) => { - first.get_or_insert(later); - } - } -} - -fn parse_directive_items<'p, S: Stage>( - cx: &mut AcceptContext, - mode: Mode, - items: impl Iterator, - is_root: bool, -) -> Option { - let condition = None; - let mut message: Option<(Span, _)> = None; - let mut label: Option<(Span, _)> = None; - let mut notes = ThinVec::new(); - let mut parent_label = None; - let mut subcommands = ThinVec::new(); - let mut append_const_msg = None; - - for item in items { - let span = item.span(); - - macro malformed() {{ - match mode { - Mode::RustcOnUnimplemented => { - cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); - } - Mode::DiagnosticOnUnimplemented => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::MalformedOnUnimplementedAttr { span }, - span, - ); - } - } - continue; - }} - - macro or_malformed($($code:tt)*) {{ - let Some(ret) = (try { - $($code)* - }) else { - - malformed!() - }; - ret - }} - - macro duplicate($name: ident, $($first_span:tt)*) {{ - match mode { - Mode::RustcOnUnimplemented => { - cx.emit_err(NoValueInOnUnimplemented { span: item.span() }); - } - Mode::DiagnosticOnUnimplemented => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - AttributeLintKind::IgnoredDiagnosticOption { - first_span: $($first_span)*, - later_span: span, - option_name: $name, - }, - span, - ); - } - } - }} - - let item: &MetaItemParser = or_malformed!(item.meta_item()?); - let name = or_malformed!(item.ident()?).name; - - // Some things like `message = "message"` must have a value. - // But with things like `append_const_msg` that is optional. - let value: Option = match item.args().name_value() { - Some(nv) => Some(or_malformed!(nv.value_as_ident()?)), - None => None, - }; - - let mut parse_format = |input: Ident| { - let snippet = cx.sess.source_map().span_to_snippet(input.span).ok(); - let is_snippet = snippet.is_some(); - match parse_format_string(input.name, snippet, input.span, mode) { - Ok((f, warnings)) => { - for warning in warnings { - let (FormatWarning::InvalidSpecifier { span, .. } - | FormatWarning::PositionalArgument { span, .. }) = warning; - cx.emit_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - AttributeLintKind::MalformedDiagnosticFormat { warning }, - span, - ); - } - - f - } - Err(e) => { - cx.emit_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - AttributeLintKind::DiagnosticWrappedParserError { - description: e.description, - label: e.label, - span: slice_span(input.span, e.span, is_snippet), - }, - input.span, - ); - // We could not parse the input, just use it as-is. - FormatString { - input: input.name, - span: input.span, - pieces: thin_vec![Piece::Lit(input.name)], - } - } - } - }; - match (mode, name) { - (_, sym::message) => { - let value = or_malformed!(value?); - if let Some(message) = &message { - duplicate!(name, message.0) - } else { - message.insert((item.span(), parse_format(value))); - } - } - (_, sym::label) => { - let value = or_malformed!(value?); - if let Some(label) = &label { - duplicate!(name, label.0) - } else { - label.insert((item.span(), parse_format(value))); - } - } - (_, sym::note) => { - let value = or_malformed!(value?); - notes.push(parse_format(value)) - } - - (Mode::RustcOnUnimplemented, sym::append_const_msg) => { - append_const_msg = if let Some(msg) = value { - Some(AppendConstMessage::Custom(msg.name, item.span())) - } else { - Some(AppendConstMessage::Default) - } - } - (Mode::RustcOnUnimplemented, sym::parent_label) => { - let value = or_malformed!(value?); - if parent_label.is_none() { - parent_label.insert(parse_format(value)); - } else { - // warn - } - } - (Mode::RustcOnUnimplemented, sym::on) => { - if is_root { - let mut items = or_malformed!(item.args().list()?); - let mut iter = items.mixed(); - let condition: &MetaItemOrLitParser = match iter.next() { - Some(c) => c, - None => { - cx.emit_err(InvalidOnClause::Empty { span }); - continue; - } - }; - - let condition = parse_condition(condition); - - if items.len() < 2 { - // Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]` - // There's a condition but no directive behind it, this is a mistake. - malformed!(); - } - - let mut directive = - or_malformed!(parse_directive_items(cx, mode, iter, false)?); - - match condition { - Ok(c) => { - directive.condition = Some(c); - subcommands.push(directive); - } - Err(e) => { - cx.emit_err(e); - } - } - } else { - malformed!(); - } - } - - _other => { - malformed!(); - } - } - } - - Some(OnUnimplementedDirective { - is_rustc_attr: matches!(mode, Mode::RustcOnUnimplemented), - condition, - subcommands, - message, - label, - notes, - parent_label, - append_const_msg, - }) -} diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 6116e40cf01e0..2c9f5fc24895f 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -26,6 +26,7 @@ use crate::attributes::confusables::*; use crate::attributes::crate_level::*; use crate::attributes::debugger::*; use crate::attributes::deprecation::*; +use crate::attributes::diagnostic::on_const::*; use crate::attributes::diagnostic::on_unimplemented::*; use crate::attributes::do_not_recommend::*; use crate::attributes::doc::*; @@ -148,6 +149,7 @@ attribute_parsers!( DocParser, MacroUseParser, NakedParser, + OnConstParser, OnUnimplementedParser, RustcCguTestAttributeParser, StabilityParser, diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index 84121edf04437..39f0d81c16582 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -1080,11 +1080,18 @@ pub enum AttributeKind { /// Represents `#[non_exhaustive]` NonExhaustive(Span), + /// Represents `#[diagnostic::on_const]`. + OnConst { + span: Span, + /// None if the directive was malformed in some way. + directive: Option>, + }, + /// Represents `#[rustc_on_unimplemented]` and `#[diagnostic::on_unimplemented]`. OnUnimplemented { span: Span, /// None if the directive was malformed in some way. - directive: Option>, + directive: Option>, }, /// Represents `#[optimize(size|speed)]` diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index dc7461cf5de1b..7ef8b6686668e 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -7,10 +7,10 @@ use thin_vec::ThinVec; use crate::attrs::PrintAttribute; #[derive(Clone, Default, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] -pub struct OnUnimplementedDirective { +pub struct Directive { pub is_rustc_attr: bool, pub condition: Option, - pub subcommands: ThinVec, + pub subcommands: ThinVec, pub message: Option<(Span, FormatString)>, pub label: Option<(Span, FormatString)>, pub notes: ThinVec, @@ -18,7 +18,7 @@ pub struct OnUnimplementedDirective { pub append_const_msg: Option, } -impl OnUnimplementedDirective { +impl Directive { /// Visit all the generic arguments used in the attribute, to see whether they are actually a /// generic of the item. If not then `visit` must issue a diagnostic. /// diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 945356804ae49..cf4e50f1ba0ff 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -77,6 +77,7 @@ impl AttributeKind { NoMangle(..) => Yes, // Needed for rustdoc NoStd(..) => No, NonExhaustive(..) => Yes, // Needed for rustdoc + OnConst { .. } => Yes, OnUnimplemented { .. } => Yes, Optimize(..) => No, PanicRuntime => No, diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 48a4f1136f65d..5aee3f382ff3c 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -449,6 +449,9 @@ pub fn decorate_attribute_lint( &AttributeLintKind::MalformedOnUnimplementedAttr { span } => { lints::MalformedOnUnimplementedAttrLint { span }.decorate_lint(diag) } + &AttributeLintKind::MalformedOnConstAttr { span } => { + lints::MalformedOnConstAttrLint { span }.decorate_lint(diag) + } AttributeLintKind::MalformedDiagnosticFormat { warning } => match warning { FormatWarning::PositionalArgument { .. } => { lints::DisallowedPositionalArgument.decorate_lint(diag) @@ -467,5 +470,8 @@ pub fn decorate_attribute_lint( &AttributeLintKind::MissingOptionsForOnUnimplemented => { lints::MissingOptionsForOnUnimplementedAttr.decorate_lint(diag) } + &AttributeLintKind::MissingOptionsForOnConst => { + lints::MissingOptionsForOnConstAttr.decorate_lint(diag) + } } } diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index a81b787ed5dd5..285a7b7f52935 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -3906,6 +3906,11 @@ pub(crate) struct IgnoredDiagnosticOption { #[help("at least one of the `message`, `note` and `label` options are expected")] pub(crate) struct MissingOptionsForOnUnimplementedAttr; +#[derive(LintDiagnostic)] +#[diag("missing options for `on_const` attribute")] +#[help("at least one of the `message`, `note` and `label` options are expected")] +pub(crate) struct MissingOptionsForOnConstAttr; + #[derive(LintDiagnostic)] #[diag("malformed `on_unimplemented` attribute")] #[help("only `message`, `note` and `label` are allowed as options")] @@ -3913,3 +3918,11 @@ pub(crate) struct MalformedOnUnimplementedAttrLint { #[label("invalid option found here")] pub span: Span, } + +#[derive(LintDiagnostic)] +#[diag("malformed `on_const` attribute")] +#[help("only `message`, `note` and `label` are allowed as options")] +pub(crate) struct MalformedOnConstAttrLint { + #[label("invalid option found here")] + pub span: Span, +} diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index f445b30979b25..1492df50a418a 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -837,6 +837,9 @@ pub enum AttributeLintKind { MalformedOnUnimplementedAttr { span: Span, }, + MalformedOnConstAttr { + span: Span, + }, MalformedDiagnosticFormat { warning: FormatWarning, }, @@ -851,6 +854,7 @@ pub enum AttributeLintKind { later_span: Span, }, MissingOptionsForOnUnimplemented, + MissingOptionsForOnConst, } #[derive(Debug, Clone, HashStable_Generic)] diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index f136c04ed9d60..b798092339009 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -20,7 +20,7 @@ use rustc_feature::{ ACCEPTED_LANG_FEATURES, AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, }; -use rustc_hir::attrs::diagnostic::OnUnimplementedDirective; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, MirDialect, MirPhase, ReprAttr, SanitizerSet, @@ -233,6 +233,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { }, Attribute::Parsed(AttributeKind::DoNotRecommend{attr_span}) => {self.check_do_not_recommend(*attr_span, hir_id, target, item)}, Attribute::Parsed(AttributeKind::OnUnimplemented{span, directive}) => {self.check_diagnostic_on_unimplemented(*span, hir_id, target,directive.as_deref())}, + Attribute::Parsed(AttributeKind::OnConst{span, ..}) => {self.check_diagnostic_on_const(*span, hir_id, target, item)} Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -386,9 +387,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Unparsed(attr_item) => { style = Some(attr_item.style); match attr.path().as_slice() { - [sym::diagnostic, sym::on_const, ..] => { - self.check_diagnostic_on_const(attr.span(), hir_id, target, item) - } [sym::autodiff_forward, ..] | [sym::autodiff_reverse, ..] => { self.check_autodiff(hir_id, attr, span, target) } @@ -611,7 +609,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { attr_span: Span, hir_id: HirId, target: Target, - directive: Option<&OnUnimplementedDirective>, + directive: Option<&Directive>, ) { if !matches!(target, Target::Trait) { self.tcx.emit_node_span_lint( @@ -689,6 +687,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> { attr_span, DiagnosticOnConstOnlyForTraitImpls { item_span }, ); + + // We don't check the validity of generic args here...whose generics would that be, anyway? + // The traits' or the impls'? } /// Checks if an `#[inline]` is applied to a function or a closure. diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 03bc823fd180b..11acc00ea702d 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -6,8 +6,7 @@ use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{ - AppendConstMessage, ConditionOptions, FormatString, OnUnimplementedDirective, - OnUnimplementedNote, Piece, + AppendConstMessage, ConditionOptions, Directive, FormatString, OnUnimplementedNote, Piece, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; @@ -335,7 +334,7 @@ fn parse_directive<'tcx>( _span: Span, is_root: bool, is_diagnostic_namespace_variant: bool, -) -> Result, ErrorGuaranteed> { +) -> Result, ErrorGuaranteed> { let mut errored = None; let item_iter = items.iter(); @@ -447,7 +446,7 @@ fn parse_directive<'tcx>( if let Some(reported) = errored { if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) } } else { - Ok(Some(OnUnimplementedDirective { + Ok(Some(Directive { is_rustc_attr: !is_diagnostic_namespace_variant, condition, subcommands, @@ -463,7 +462,7 @@ fn parse_directive<'tcx>( pub fn of_item_directive<'tcx>( tcx: TyCtxt<'tcx>, item_def_id: DefId, -) -> Result, ErrorGuaranteed> { +) -> Result, ErrorGuaranteed> { let attr = if tcx.is_trait(item_def_id) { sym::on_unimplemented } else if let DefKind::Impl { of_trait: true } = tcx.def_kind(item_def_id) { @@ -477,10 +476,12 @@ pub fn of_item_directive<'tcx>( }; if attr == sym::on_unimplemented { Ok(find_attr!(tcx, item_def_id, AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref().cloned()).flatten()) + } else if attr == sym::on_const { + Ok(find_attr!(tcx, item_def_id, AttributeKind::OnConst {directive, ..} => directive.as_deref().cloned()).flatten()) } else { tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) .filter_map(|attr| parse_attribute_directive(attr, true, tcx, item_def_id).transpose()) - .try_fold(None, |aggr: Option, directive| { + .try_fold(None, |aggr: Option, directive| { let directive = directive?; if let Some(aggr) = aggr { let mut subcommands = aggr.subcommands; @@ -527,7 +528,7 @@ pub fn of_item_directive<'tcx>( "append_const_msg", ); - Ok(Some(OnUnimplementedDirective { + Ok(Some(Directive { is_rustc_attr: false, condition: aggr.condition.or(directive.condition), subcommands, @@ -549,7 +550,7 @@ fn parse_attribute_directive<'tcx>( is_diagnostic_namespace_variant: bool, tcx: TyCtxt<'tcx>, item_def_id: DefId, -) -> Result, ErrorGuaranteed> { +) -> Result, ErrorGuaranteed> { let result = if let Some(items) = attr.meta_item_list() { parse_directive( tcx, @@ -561,7 +562,7 @@ fn parse_attribute_directive<'tcx>( ) } else if let Some(value) = attr.value_str() { if !is_diagnostic_namespace_variant { - Ok(Some(OnUnimplementedDirective { + Ok(Some(Directive { is_rustc_attr: !is_diagnostic_namespace_variant, condition: None, message: None, @@ -633,7 +634,7 @@ fn parse_attribute_directive<'tcx>( } pub(crate) fn evaluate_directive<'tcx>( - slf: &OnUnimplementedDirective, + slf: &Directive, tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, condition_options: &ConditionOptions, diff --git a/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs b/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs new file mode 100644 index 0000000000000..cf854a9072a6a --- /dev/null +++ b/tests/ui/diagnostic_namespace/on_const/auxiliary/const_trait.rs @@ -0,0 +1,10 @@ +#![feature(diagnostic_on_const)] + +pub struct X; + +#[diagnostic::on_const(message = "message", label = "label", note = "note")] +impl PartialEq for X { + fn eq(&self, _other: &X) -> bool { + true + } +} diff --git a/tests/ui/diagnostic_namespace/on_const/misplaced_attr.rs b/tests/ui/diagnostic_namespace/on_const/misplaced_attr.rs index f7babae3aa7c4..48654ed47ec4d 100644 --- a/tests/ui/diagnostic_namespace/on_const/misplaced_attr.rs +++ b/tests/ui/diagnostic_namespace/on_const/misplaced_attr.rs @@ -1,4 +1,4 @@ -#![feature(const_trait_impl, const_cmp)] +#![feature(const_trait_impl, const_cmp, diagnostic_on_const)] #![deny(misplaced_diagnostic_attributes)] #[diagnostic::on_const(message = "tadaa", note = "boing")] diff --git a/tests/ui/unpretty/diagnostic-attr.rs b/tests/ui/unpretty/diagnostic-attr.rs index 4ef85c71f90e2..77c58a7025d06 100644 --- a/tests/ui/unpretty/diagnostic-attr.rs +++ b/tests/ui/unpretty/diagnostic-attr.rs @@ -1,6 +1,7 @@ //@ compile-flags: -Zunpretty=hir //@ check-pass //@ edition: 2015 +#![feature(diagnostic_on_const)] #[diagnostic::on_unimplemented( message = "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", @@ -12,3 +13,13 @@ pub trait ImportantTrait {} #[diagnostic::do_not_recommend] impl ImportantTrait for T where T: Clone {} + +pub struct X; + +#[diagnostic::on_const( + message = "My Message for `ImportantTrait` implemented for `{Self}`", + label = "My Label", + note = "Note 1", + note = "Note 2" +)] +impl ImportantTrait for X {} diff --git a/tests/ui/unpretty/diagnostic-attr.stdout b/tests/ui/unpretty/diagnostic-attr.stdout index 2258029f7e13b..93a4717376fec 100644 --- a/tests/ui/unpretty/diagnostic-attr.stdout +++ b/tests/ui/unpretty/diagnostic-attr.stdout @@ -1,11 +1,12 @@ -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; //@ compile-flags: -Zunpretty=hir //@ check-pass //@ edition: 2015 +#![feature(diagnostic_on_const)] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; -#[attr = OnUnimplemented {directive: OnUnimplementedDirective {is_rustc_attr: false, +#[attr = OnUnimplemented {directive: Directive {is_rustc_attr: false, subcommands: [], message: FormatString {input: "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", pieces: [Lit("My Message for `ImportantTrait<"), @@ -18,3 +19,14 @@ trait ImportantTrait { } #[attr = DoNotRecommend] impl ImportantTrait for T where T: Clone { } + +struct X; + +#[attr = OnConst {directive: Directive {is_rustc_attr: false, subcommands: [], +message: FormatString {input: "My Message for `ImportantTrait` implemented for `{Self}`", +pieces: [Lit("My Message for `ImportantTrait` implemented for `"), +Arg(SelfUpper), Lit("`")]}, label: FormatString {input: "My Label", +pieces: [Lit("My Label")]}, notes: [FormatString {input: "Note 1", +pieces: [Lit("Note 1")]}, FormatString {input: "Note 2", +pieces: [Lit("Note 2")]}]}}] +impl ImportantTrait for X { } From 670fc8cc0af1ce99f8fd3a1e5338b9b495855c48 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:01:24 +0100 Subject: [PATCH 15/32] Delete unused code --- Cargo.lock | 1 - .../attributes/diagnostic/on_unimplemented.rs | 23 +- .../rustc_hir_analysis/src/check/check.rs | 7 - compiler/rustc_trait_selection/Cargo.toml | 1 - .../traits/fulfillment_errors.rs | 8 +- .../traits/on_unimplemented.rs | 490 +----------------- .../traits/on_unimplemented_format.rs | 164 +----- 7 files changed, 34 insertions(+), 660 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 925d9991910f0..62de52ca49670 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4682,7 +4682,6 @@ dependencies = [ "rustc_macros", "rustc_middle", "rustc_next_trait_solver", - "rustc_parse_format", "rustc_session", "rustc_span", "rustc_transmute", diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 13ef09051fd7c..ae3da2525ba2d 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -8,6 +8,7 @@ use crate::attributes::template; #[derive(Default)] pub(crate) struct OnUnimplementedParser { + span: Option, directive: Option<(Span, Directive)>, } @@ -19,6 +20,11 @@ impl OnUnimplementedParser { mode: Mode, ) { let span = cx.attr_span; + self.span = Some(span); + if !matches!(cx.target, Target::Trait) { + // Lint later emitted in check_attr + return; + } let items = match args { ArgParser::List(items) if items.len() != 0 => items, @@ -40,10 +46,9 @@ impl OnUnimplementedParser { } }; - let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) else { - return; + if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) { + merge_directives(cx, &mut self.directive, (span, directive)); }; - merge_directives(cx, &mut self.directive, (span, directive)); } } @@ -63,9 +68,13 @@ impl AttributeParser for OnUnimplementedParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { - self.directive.map(|(span, directive)| AttributeKind::OnUnimplemented { - span, - directive: Some(Box::new(directive)), - }) + if let Some(span) = self.span { + Some(AttributeKind::OnUnimplemented { + span, + directive: self.directive.map(|d| Box::new(d.1)), + }) + } else { + None + } } } diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index 950a90c54e184..2068fc0ab978c 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -26,7 +26,6 @@ use rustc_session::lint::builtin::UNINHABITED_STATIC; use rustc_span::source_map::Spanned; use rustc_target::spec::{AbiMap, AbiMapping}; use rustc_trait_selection::error_reporting::InferCtxtErrorExt; -use rustc_trait_selection::error_reporting::traits::on_unimplemented::of_item_directive; use rustc_trait_selection::traits; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use tracing::{debug, instrument}; @@ -805,7 +804,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), tcx.ensure_ok().type_of(def_id); tcx.ensure_ok().predicates_of(def_id); tcx.ensure_ok().associated_items(def_id); - check_diagnostic_attrs(tcx, def_id); if of_trait { let impl_trait_header = tcx.impl_trait_header(def_id); res = res.and( @@ -1121,11 +1119,6 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), }) } -pub(super) fn check_diagnostic_attrs(tcx: TyCtxt<'_>, def_id: LocalDefId) { - // an error would be reported if this fails. - let _ = of_item_directive(tcx, def_id.to_def_id()); -} - pub(super) fn check_specialization_validity<'tcx>( tcx: TyCtxt<'tcx>, trait_def: &ty::TraitDef, diff --git a/compiler/rustc_trait_selection/Cargo.toml b/compiler/rustc_trait_selection/Cargo.toml index cd61a8f654733..0ba46c6ddd56a 100644 --- a/compiler/rustc_trait_selection/Cargo.toml +++ b/compiler/rustc_trait_selection/Cargo.toml @@ -15,7 +15,6 @@ rustc_infer = { path = "../rustc_infer" } rustc_macros = { path = "../rustc_macros" } rustc_middle = { path = "../rustc_middle" } rustc_next_trait_solver = { path = "../rustc_next_trait_solver" } -rustc_parse_format = { path = "../rustc_parse_format" } rustc_session = { path = "../rustc_session" } rustc_span = { path = "../rustc_span" } rustc_transmute = { path = "../rustc_transmute", features = ["rustc"] } diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 118c7ed9380f4..60725b6dc4e80 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -14,10 +14,11 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions, msg, pluralize, struct_span_code_err, }; +use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedNote}; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::intravisit::Visitor; -use rustc_hir::{self as hir, LangItem, Node}; +use rustc_hir::{self as hir, LangItem, Node, find_attr}; use rustc_infer::infer::{InferOk, TypeTrace}; use rustc_infer::traits::ImplSource; use rustc_infer::traits::solve::Goal; @@ -44,7 +45,7 @@ use super::{ }; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::infer::TyCategory; -use crate::error_reporting::traits::on_unimplemented::{evaluate_directive, of_item_directive}; +use crate::error_reporting::traits::on_unimplemented::evaluate_directive; use crate::error_reporting::traits::report_dyn_incompatibility; use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch, CoroClosureNotFn}; use crate::infer::{self, InferCtxt, InferCtxtExt as _}; @@ -912,10 +913,9 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { diag.long_ty_path(), ); - if let Ok(Some(command)) = of_item_directive(self.tcx, impl_did) { + if let Some(command) = find_attr!(self.tcx.get_all_attrs(impl_did), AttributeKind::OnConst {directive, ..} => directive.as_deref()).flatten(){ let note = evaluate_directive( &command, - self.tcx, predicate.skip_binder().trait_ref, &condition_options, &format_args, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 11acc00ea702d..955c9e04a1297 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -1,33 +1,20 @@ use std::path::PathBuf; -use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit}; -use rustc_errors::codes::*; -use rustc_errors::{ErrorGuaranteed, struct_span_code_err}; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; -use rustc_hir::attrs::diagnostic::{ - AppendConstMessage, ConditionOptions, Directive, FormatString, OnUnimplementedNote, Piece, -}; -use rustc_hir::def::DefKind; -use rustc_hir::def_id::{DefId, LocalDefId}; +use rustc_hir::attrs::diagnostic::{ConditionOptions, Directive, OnUnimplementedNote}; +use rustc_hir::def_id::LocalDefId; +use rustc_hir::find_attr; pub use rustc_hir::lints::FormatWarning; -use rustc_hir::{AttrArgs, Attribute, find_attr}; -use rustc_macros::LintDiagnostic; -use rustc_middle::bug; use rustc_middle::ty::print::PrintTraitRefExt; -use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind, TyCtxt}; -use rustc_session::lint::builtin::{ - MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, -}; -use rustc_span::{Span, Symbol, sym}; -use thin_vec::{ThinVec, thin_vec}; +use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind}; +use rustc_span::Symbol; use tracing::{debug, info}; use super::{ObligationCauseCode, PredicateObligation}; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::traits::on_unimplemented_condition::matches_predicate; -use crate::error_reporting::traits::on_unimplemented_format::{Ctx, FormatArgs}; -use crate::errors::NoValueInOnUnimplemented; +use crate::error_reporting::traits::on_unimplemented_format::FormatArgs; impl<'tcx> TypeErrCtxt<'_, 'tcx> { /// Used to set on_unimplemented's `ItemContext` @@ -60,10 +47,9 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } let (condition_options, format_args) = self.on_unimplemented_components(trait_pred, obligation, long_ty_path); - if let Ok(Some(command)) = of_item_directive(self.tcx, trait_pred.def_id()) { + if let Some(command) = find_attr!(self.tcx.get_all_attrs( trait_pred.def_id()), AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { evaluate_directive( &command, - self.tcx, trait_pred.skip_binder().trait_ref, &condition_options, &format_args, @@ -270,372 +256,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } } -#[derive(LintDiagnostic)] -#[diag("malformed `on_unimplemented` attribute")] -#[help("only `message`, `note` and `label` are allowed as options")] -pub struct MalformedOnUnimplementedAttrLint { - #[label("invalid option found here")] - pub span: Span, -} - -impl MalformedOnUnimplementedAttrLint { - pub fn new(span: Span) -> Self { - Self { span } - } -} - -#[derive(LintDiagnostic)] -#[diag("missing options for `on_unimplemented` attribute")] -#[help("at least one of the `message`, `note` and `label` options are expected")] -pub struct MissingOptionsForOnUnimplementedAttr; - -#[derive(LintDiagnostic)] -#[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")] -pub(crate) struct IgnoredDiagnosticOption { - pub option_name: &'static str, - #[label("`{$option_name}` is first declared here")] - pub first_span: Span, - #[label("`{$option_name}` is later redundantly declared here")] - pub later_span: Span, -} - -impl IgnoredDiagnosticOption { - fn maybe_emit_warning<'tcx>( - tcx: TyCtxt<'tcx>, - item_def_id: DefId, - new: Option, - old: Option, - option_name: &'static str, - ) { - if let (Some(new_item), Some(old_item)) = (new, old) - && let Some(item_def_id) = item_def_id.as_local() - { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - new_item, - IgnoredDiagnosticOption { later_span: new_item, first_span: old_item, option_name }, - ); - } - } -} - -#[derive(LintDiagnostic)] -#[diag("{$description}")] -pub struct WrappedParserError { - pub description: String, - pub label: String, -} - -fn parse_directive<'tcx>( - tcx: TyCtxt<'tcx>, - item_def_id: DefId, - items: &[MetaItemInner], - _span: Span, - is_root: bool, - is_diagnostic_namespace_variant: bool, -) -> Result, ErrorGuaranteed> { - let mut errored = None; - let item_iter = items.iter(); - - let parse_value = |value_str, span| { - try_parse_format_string(tcx, item_def_id, value_str, span, is_diagnostic_namespace_variant) - .map(Some) - }; - - let condition = if is_root { None } else { unreachable!() }; - - let mut message = None; - let mut label = None; - let mut notes = ThinVec::new(); - let mut parent_label = None; - let mut subcommands = ThinVec::new(); - let mut append_const_msg = None; - - let get_value_and_span = |item: &_, key| { - if let MetaItemInner::MetaItem(MetaItem { - path, - kind: MetaItemKind::NameValue(MetaItemLit { span, kind: LitKind::Str(s, _), .. }), - .. - }) = item - && *path == key - { - Some((*s, *span)) - } else { - None - } - }; - - for item in item_iter { - if let Some((message_, span)) = get_value_and_span(item, sym::message) - && message.is_none() - { - message = parse_value(message_, span)?.map(|l| (item.span(), l)); - continue; - } else if let Some((label_, span)) = get_value_and_span(item, sym::label) - && label.is_none() - { - label = parse_value(label_, span)?.map(|l| (item.span(), l)); - continue; - } else if let Some((note_, span)) = get_value_and_span(item, sym::note) { - if let Some(note) = parse_value(note_, span)? { - notes.push(note); - continue; - } - } else if item.has_name(sym::parent_label) - && parent_label.is_none() - && !is_diagnostic_namespace_variant - { - if let Some(parent_label_) = item.value_str() { - parent_label = parse_value(parent_label_, item.span())?; - continue; - } - } else if item.has_name(sym::on) - && is_root - && message.is_none() - && label.is_none() - && notes.is_empty() - && !is_diagnostic_namespace_variant - // FIXME(diagnostic_namespace): disallow filters for now - { - if let Some(items) = item.meta_item_list() { - match parse_directive( - tcx, - item_def_id, - items, - item.span(), - false, - is_diagnostic_namespace_variant, - ) { - Ok(Some(subcommand)) => subcommands.push(subcommand), - Ok(None) => bug!( - "This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false" - ), - Err(reported) => errored = Some(reported), - }; - continue; - } - } else if item.has_name(sym::append_const_msg) - && append_const_msg.is_none() - && !is_diagnostic_namespace_variant - { - if let Some(msg) = item.value_str() { - append_const_msg = Some(AppendConstMessage::Custom(msg, item.span())); - continue; - } else if item.is_word() { - append_const_msg = Some(AppendConstMessage::Default); - continue; - } - } - - if is_diagnostic_namespace_variant { - if let Some(def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(def_id), - vec![item.span()], - MalformedOnUnimplementedAttrLint::new(item.span()), - ); - } - } else { - // nothing found - tcx.dcx().emit_err(NoValueInOnUnimplemented { span: item.span() }); - } - } - - if let Some(reported) = errored { - if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) } - } else { - Ok(Some(Directive { - is_rustc_attr: !is_diagnostic_namespace_variant, - condition, - subcommands, - message, - label, - notes, - parent_label, - append_const_msg, - })) - } -} - -pub fn of_item_directive<'tcx>( - tcx: TyCtxt<'tcx>, - item_def_id: DefId, -) -> Result, ErrorGuaranteed> { - let attr = if tcx.is_trait(item_def_id) { - sym::on_unimplemented - } else if let DefKind::Impl { of_trait: true } = tcx.def_kind(item_def_id) { - sym::on_const - } else { - // It could be a trait_alias (`trait MyTrait = SomeOtherTrait`) - // or an implementation (`impl MyTrait for Foo {}`) - // - // We don't support those. - return Ok(None); - }; - if attr == sym::on_unimplemented { - Ok(find_attr!(tcx, item_def_id, AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref().cloned()).flatten()) - } else if attr == sym::on_const { - Ok(find_attr!(tcx, item_def_id, AttributeKind::OnConst {directive, ..} => directive.as_deref().cloned()).flatten()) - } else { - tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr]) - .filter_map(|attr| parse_attribute_directive(attr, true, tcx, item_def_id).transpose()) - .try_fold(None, |aggr: Option, directive| { - let directive = directive?; - if let Some(aggr) = aggr { - let mut subcommands = aggr.subcommands; - subcommands.extend(directive.subcommands); - let mut notes = aggr.notes; - notes.extend(directive.notes); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.message.as_ref().map(|f| f.0), - aggr.message.as_ref().map(|f| f.0), - "message", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.label.as_ref().map(|f| f.0), - aggr.label.as_ref().map(|f| f.0), - "label", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.condition.as_ref().map(|i| i.span), - aggr.condition.as_ref().map(|i| i.span), - "condition", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.parent_label.as_ref().map(|f| f.span), - aggr.parent_label.as_ref().map(|f| f.span), - "parent_label", - ); - IgnoredDiagnosticOption::maybe_emit_warning( - tcx, - item_def_id, - directive.append_const_msg.as_ref().and_then(|c| { - if let AppendConstMessage::Custom(_, s) = c { Some(*s) } else { None } - }), - aggr.append_const_msg.as_ref().and_then(|c| { - if let AppendConstMessage::Custom(_, s) = c { Some(*s) } else { None } - }), - "append_const_msg", - ); - - Ok(Some(Directive { - is_rustc_attr: false, - condition: aggr.condition.or(directive.condition), - subcommands, - message: aggr.message.or(directive.message), - label: aggr.label.or(directive.label), - notes, - parent_label: aggr.parent_label.or(directive.parent_label), - append_const_msg: aggr.append_const_msg.or(directive.append_const_msg), - })) - } else { - Ok(Some(directive)) - } - }) - } -} - -fn parse_attribute_directive<'tcx>( - attr: &Attribute, - is_diagnostic_namespace_variant: bool, - tcx: TyCtxt<'tcx>, - item_def_id: DefId, -) -> Result, ErrorGuaranteed> { - let result = if let Some(items) = attr.meta_item_list() { - parse_directive( - tcx, - item_def_id, - &items, - attr.span(), - true, - is_diagnostic_namespace_variant, - ) - } else if let Some(value) = attr.value_str() { - if !is_diagnostic_namespace_variant { - Ok(Some(Directive { - is_rustc_attr: !is_diagnostic_namespace_variant, - condition: None, - message: None, - subcommands: thin_vec![], - label: Some(( - attr.span(), - try_parse_format_string( - tcx, - item_def_id, - value, - attr.value_span().unwrap_or(attr.span()), - is_diagnostic_namespace_variant, - )?, - )), - notes: thin_vec![], - parent_label: None, - append_const_msg: None, - })) - } else { - let item = attr.get_normal_item(); - let report_span = match &item.args { - AttrArgs::Empty => item.path.span, - AttrArgs::Delimited(args) => args.dspan.entire(), - AttrArgs::Eq { eq_span, expr } => eq_span.to(expr.span), - }; - - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - report_span, - MalformedOnUnimplementedAttrLint::new(report_span), - ); - } - Ok(None) - } - } else if is_diagnostic_namespace_variant { - match attr { - Attribute::Unparsed(p) if !matches!(p.args, AttrArgs::Empty) => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - attr.span(), - MalformedOnUnimplementedAttrLint::new(attr.span()), - ); - } - } - _ => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_ATTRIBUTES, - tcx.local_def_id_to_hir_id(item_def_id), - attr.span(), - MissingOptionsForOnUnimplementedAttr, - ) - } - } - }; - - Ok(None) - } else { - let reported = - tcx.dcx().delayed_bug("of_item_directive: neither meta_item_list nor value_str"); - return Err(reported); - }; - debug!("of_item_directive({:?}) = {:?}", item_def_id, result); - result -} - pub(crate) fn evaluate_directive<'tcx>( slf: &Directive, - tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, condition_options: &ConditionOptions, args: &FormatArgs<'tcx>, @@ -676,99 +298,13 @@ pub(crate) fn evaluate_directive<'tcx>( append_const_msg = command.append_const_msg; } + use crate::error_reporting::traits::on_unimplemented_format::format; + OnUnimplementedNote { - label: label.map(|l| format_directive(l.1, tcx, trait_ref, args)), - message: message.map(|m| format_directive(m.1, tcx, trait_ref, args)), - notes: notes.into_iter().map(|n| format_directive(n, tcx, trait_ref, args)).collect(), - parent_label: parent_label.map(|e_s| format_directive(e_s, tcx, trait_ref, args)), + label: label.map(|l| format(&l.1, args)), + message: message.map(|m| format(&m.1, args)), + notes: notes.into_iter().map(|n| format(&n, args)).collect(), + parent_label: parent_label.map(|e_s| format(&e_s, args)), append_const_msg, } } - -fn try_parse_format_string<'tcx>( - tcx: TyCtxt<'tcx>, - trait_def_id: DefId, - input: Symbol, - span: Span, - is_diagnostic_namespace_variant: bool, -) -> Result { - let ctx = if is_diagnostic_namespace_variant { - Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id } - } else { - Ctx::RustcOnUnimplemented { tcx, trait_def_id } - }; - - let mut result = Ok(()); - - use crate::error_reporting::traits::on_unimplemented_format::parse_format_string; - - let snippet = tcx.sess.source_map().span_to_snippet(span).ok(); - let ret = match parse_format_string(input, snippet, span, &ctx) { - // Warnings about format specifiers, deprecated parameters, wrong parameters etc. - // In other words we'd like to let the author know, but we can still try to format the string later - Ok((f, warnings)) => { - if is_diagnostic_namespace_variant { - for w in warnings { - use crate::error_reporting::traits::on_unimplemented_format::emit_warning; - - emit_warning(&w, tcx, trait_def_id) - } - } else { - for w in warnings { - match w { - FormatWarning::PositionalArgument { span, .. } => { - let reported = struct_span_code_err!( - tcx.dcx(), - span, - E0231, - "positional format arguments are not allowed here" - ) - .emit(); - result = Err(reported); - } - FormatWarning::InvalidSpecifier { .. } => {} - } - } - }; - f - } - // Error from the underlying `rustc_parse_format::Parser` - Err(e) => { - // we cannot return errors from processing the format string as hard error here - // as the diagnostic namespace guarantees that malformed input cannot cause an error - // - // if we encounter any error while processing we nevertheless want to show it as warning - // so that users are aware that something is not correct - if is_diagnostic_namespace_variant { - if let Some(trait_def_id) = trait_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(trait_def_id), - span, - WrappedParserError { description: e.description, label: e.label }, - ); - } - } else { - let reported = - struct_span_code_err!(tcx.dcx(), span, E0231, "{}", e.description).emit(); - result = Err(reported); - } - - // We could not parse the input, just use it as-is. - FormatString { input, span, pieces: thin_vec![Piece::Lit(input)] } - } - }; - - result?; - Ok(ret) -} - -pub fn format_directive<'tcx>( - slf: FormatString, - _tcx: TyCtxt<'tcx>, - _trait_ref: ty::TraitRef<'tcx>, - args: &FormatArgs<'tcx>, -) -> String { - use crate::error_reporting::traits::on_unimplemented_format::format; - format(&slf, args) -} diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs index 376a5a6098c37..a68b17dc2f4cc 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs @@ -1,49 +1,8 @@ use std::fmt; -use std::ops::Range; use rustc_hir::attrs::diagnostic::*; -use rustc_hir::lints::FormatWarning; -use rustc_middle::ty::TyCtxt; use rustc_middle::ty::print::TraitRefPrintSugared; -use rustc_parse_format::{ - Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position, -}; -use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_FORMAT_LITERALS; -use rustc_span::def_id::DefId; -use rustc_span::{InnerSpan, Span, Symbol, kw, sym}; - -use crate::error_reporting::traits::on_unimplemented_format::errors::*; -pub enum Ctx<'tcx> { - // `#[rustc_on_unimplemented]` - RustcOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, - // `#[diagnostic::...]` - DiagnosticOnUnimplemented { tcx: TyCtxt<'tcx>, trait_def_id: DefId }, -} - -pub fn emit_warning<'tcx>(slf: &FormatWarning, tcx: TyCtxt<'tcx>, item_def_id: DefId) { - match *slf { - FormatWarning::PositionalArgument { span, .. } => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - DisallowedPositionalArgument, - ); - } - } - FormatWarning::InvalidSpecifier { span, .. } => { - if let Some(item_def_id) = item_def_id.as_local() { - tcx.emit_node_span_lint( - MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, - tcx.local_def_id_to_hir_id(item_def_id), - span, - InvalidFormatSpecifier, - ); - } - } - } -} +use rustc_span::{Symbol, kw}; /// Arguments to fill a [FormatString] with. /// @@ -87,36 +46,6 @@ pub struct FormatArgs<'tcx> { pub generic_args: Vec<(Symbol, String)>, } -pub fn parse_format_string<'tcx>( - input: Symbol, - snippet: Option, - span: Span, - ctx: &Ctx<'tcx>, -) -> Result<(FormatString, Vec), ParseError> { - let s = input.as_str(); - let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic); - let pieces: Vec<_> = parser.by_ref().collect(); - - if let Some(err) = parser.errors.into_iter().next() { - return Err(err); - } - let mut warnings = Vec::new(); - - let pieces = pieces - .into_iter() - .map(|piece| match piece { - RpfPiece::Lit(lit) => Piece::Lit(Symbol::intern(lit)), - RpfPiece::NextArgument(arg) => { - warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal); - let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal); - Piece::Arg(arg) - } - }) - .collect(); - - Ok((FormatString { input, pieces, span }, warnings)) -} - pub fn format(slf: &FormatString, args: &FormatArgs<'_>) -> String { let mut ret = String::new(); for piece in &slf.pieces { @@ -154,94 +83,3 @@ pub fn format(slf: &FormatString, args: &FormatArgs<'_>) -> String { } ret } - -fn parse_arg<'tcx>( - arg: &Argument<'_>, - ctx: &Ctx<'tcx>, - warnings: &mut Vec, - input_span: Span, - is_source_literal: bool, -) -> FormatArg { - let (Ctx::RustcOnUnimplemented { tcx, trait_def_id } - | Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = *ctx; - - let span = slice_span(input_span, arg.position_span.clone(), is_source_literal); - - match arg.position { - // Something like "hello {name}" - Position::ArgumentNamed(name) => match (ctx, Symbol::intern(name)) { - // Only `#[rustc_on_unimplemented]` can use these - (Ctx::RustcOnUnimplemented { .. }, sym::ItemContext) => FormatArg::ItemContext, - (Ctx::RustcOnUnimplemented { .. }, sym::This) => FormatArg::This, - (Ctx::RustcOnUnimplemented { .. }, sym::Trait) => FormatArg::Trait, - // Any attribute can use these - ( - Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, - kw::SelfUpper, - ) => FormatArg::SelfUpper, - ( - Ctx::RustcOnUnimplemented { .. } | Ctx::DiagnosticOnUnimplemented { .. }, - generic_param, - ) => FormatArg::GenericParam { generic_param, span }, - }, - - // `{:1}` and `{}` are ignored - Position::ArgumentIs(idx) => { - warnings.push(FormatWarning::PositionalArgument { - span, - help: format!("use `{{{idx}}}` to print a number in braces"), - }); - FormatArg::AsIs(Symbol::intern(&format!("{{{idx}}}"))) - } - Position::ArgumentImplicitlyIs(_) => { - warnings.push(FormatWarning::PositionalArgument { - span, - help: String::from("use `{{}}` to print empty braces"), - }); - FormatArg::AsIs(sym::empty_braces) - } - } -} - -/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything -/// with specifiers, so emit a warning if they are used. -fn warn_on_format_spec( - spec: &FormatSpec<'_>, - warnings: &mut Vec, - input_span: Span, - is_source_literal: bool, -) { - if spec.ty != "" { - let span = spec - .ty_span - .as_ref() - .map(|inner| slice_span(input_span, inner.clone(), is_source_literal)) - .unwrap_or(input_span); - warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() }) - } -} - -fn slice_span(input: Span, Range { start, end }: Range, is_source_literal: bool) -> Span { - if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input } -} - -pub mod errors { - use rustc_macros::LintDiagnostic; - - #[derive(LintDiagnostic)] - #[diag("positional format arguments are not allowed here")] - #[help( - "only named format arguments with the name of one of the generic types are allowed in this context" - )] - pub struct DisallowedPositionalArgument; - - #[derive(LintDiagnostic)] - #[diag("invalid format specifier")] - #[help("no format specifier are supported in this position")] - pub struct InvalidFormatSpecifier; - - #[derive(LintDiagnostic)] - #[diag("missing options for `on_unimplemented` attribute")] - #[help("at least one of the `message`, `note` and `label` options are expected")] - pub struct MissingOptionsForOnUnimplementedAttr; -} From c02e25125700e9c48178bbbce42ebb0c05d0953b Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:18:58 +0100 Subject: [PATCH 16/32] Dele e0231 --- .../src/error_codes/E0231.md | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/compiler/rustc_error_codes/src/error_codes/E0231.md b/compiler/rustc_error_codes/src/error_codes/E0231.md index b22e3c7082a8a..5c644c044e41f 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0231.md +++ b/compiler/rustc_error_codes/src/error_codes/E0231.md @@ -1,24 +1 @@ -The `#[rustc_on_unimplemented]` attribute lets you specify a custom error -message for when a particular trait isn't implemented on a type placed in a -position that needs that trait. For example, when the following code is -compiled: - -```compile_fail,E0231 -#![feature(rustc_attrs)] -#![allow(internal_features)] - -#[rustc_on_unimplemented = "error on `{Self}` with params `<{A},{}>`"] // error! -trait BadAnnotation {} -``` - -there will be an error about `bool` not implementing `Index`, followed by a -note saying "the type `bool` cannot be indexed by `u8`". - -As you can see, you can specify type parameters in curly braces for -instantiation with the actual types (using the regular format string syntax) in -a given situation. Furthermore, `{Self}` will be instantiated to the type (in -this case, `bool`) that we tried to use. - -This error appears when the curly braces do not contain an identifier. Please -add one of the same name as a type parameter. If you intended to use literal -braces, use `{{` and `}}` to escape them. +#### Note: this error code is no longer emitted by the compiler From 8153a5f156c44b51514abf23b7d0b511b58b5e98 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 00:19:06 +0100 Subject: [PATCH 17/32] Move diagnostic formatting logic to rustc_hir --- compiler/rustc_hir/src/attrs/diagnostic.rs | 167 +++++++++++++++++- .../traits/fulfillment_errors.rs | 6 +- .../src/error_reporting/traits/mod.rs | 2 - .../traits/on_unimplemented.rs | 65 +------ .../traits/on_unimplemented_condition.rs | 30 ---- .../traits/on_unimplemented_format.rs | 85 --------- 6 files changed, 169 insertions(+), 186 deletions(-) delete mode 100644 compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs delete mode 100644 compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 7ef8b6686668e..d055770a8bdd0 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -1,8 +1,12 @@ //! Contains the data structures used by the diagnostic attribute family. +use std::fmt; +use std::fmt::Debug; + pub use rustc_ast::attr::data_structures::*; use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; -use rustc_span::{DesugaringKind, Span, Symbol}; +use rustc_span::{DesugaringKind, Span, Symbol, kw}; use thin_vec::ThinVec; +use tracing::{debug, info}; use crate::attrs::PrintAttribute; @@ -48,9 +52,59 @@ impl Directive { parent_label.visit_params(visit); } } + + pub fn evaluate_directive( + &self, + trait_name: impl Debug, + condition_options: &ConditionOptions, + args: &FormatArgs, + ) -> OnUnimplementedNote { + let mut message = None; + let mut label = None; + let mut notes = Vec::new(); + let mut parent_label = None; + let mut append_const_msg = None; + info!( + "evaluate_directive({:?}, trait_ref={:?}, options={:?}, args ={:?})", + self, trait_name, condition_options, args + ); + + for command in self.subcommands.iter().chain(Some(self)).rev() { + debug!(?command); + if let Some(ref condition) = command.condition + && !condition.matches_predicate(condition_options) + { + debug!("evaluate_directive: skipping {:?} due to condition", command); + continue; + } + debug!("evaluate_directive: {:?} succeeded", command); + if let Some(ref message_) = command.message { + message = Some(message_.clone()); + } + + if let Some(ref label_) = command.label { + label = Some(label_.clone()); + } + + notes.extend(command.notes.clone()); + + if let Some(ref parent_label_) = command.parent_label { + parent_label = Some(parent_label_.clone()); + } + + append_const_msg = command.append_const_msg; + } + + OnUnimplementedNote { + label: label.map(|l| l.1.format(args)), + message: message.map(|m| m.1.format(args)), + notes: notes.into_iter().map(|n| n.format(args)).collect(), + parent_label: parent_label.map(|e_s| e_s.format(args)), + append_const_msg, + } + } } -/// For the `#[rustc_on_unimplemented]` attribute #[derive(Default, Debug)] pub struct OnUnimplementedNote { pub message: Option, @@ -88,8 +142,45 @@ pub struct FormatString { pub span: Span, pub pieces: ThinVec, } - impl FormatString { + pub fn format(&self, args: &FormatArgs) -> String { + let mut ret = String::new(); + for piece in &self.pieces { + match piece { + Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(s.as_str()), + + // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` + Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { + match args.generic_args.iter().find(|(p, _)| p == generic_param) { + Some((_, val)) => ret.push_str(val.as_str()), + + None => { + // Apparently this was not actually a generic parameter, so lets write + // what the user wrote. + let _ = fmt::write(&mut ret, format_args!("{{{generic_param}}}")); + } + } + } + // `{Self}` + Piece::Arg(FormatArg::SelfUpper) => { + let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { + Some((_, val)) => val.to_string(), + None => "Self".to_string(), + }; + ret.push_str(&slf); + } + + // It's only `rustc_onunimplemented` from here + Piece::Arg(FormatArg::This) => ret.push_str(&args.this), + Piece::Arg(FormatArg::Trait) => { + let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); + } + Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), + } + } + ret + } + fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { for piece in &self.pieces { if let Piece::Arg(FormatArg::GenericParam { generic_param, span }) = piece { @@ -99,6 +190,48 @@ impl FormatString { } } +/// Arguments to fill a [FormatString] with. +/// +/// For example, given a +/// ```rust,ignore (just an example) +/// +/// #[rustc_on_unimplemented( +/// on(all(from_desugaring = "QuestionMark"), +/// message = "the `?` operator can only be used in {ItemContext} \ +/// that returns `Result` or `Option` \ +/// (or another type that implements `{FromResidual}`)", +/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", +/// parent_label = "this function should return `Result` or `Option` to accept `?`" +/// ), +/// )] +/// pub trait FromResidual::Residual> { +/// ... +/// } +/// +/// async fn an_async_function() -> u32 { +/// let x: Option = None; +/// x?; //~ ERROR the `?` operator +/// 22 +/// } +/// ``` +/// it will look like this: +/// +/// ```rust,ignore (just an example) +/// FormatArgs { +/// this: "FromResidual", +/// trait_sugared: "FromResidual>", +/// item_context: "an async function", +/// generic_args: [("Self", "u32"), ("R", "Option")], +/// } +/// ``` +#[derive(Debug)] +pub struct FormatArgs { + pub this: String, + pub trait_sugared: String, + pub item_context: &'static str, + pub generic_args: Vec<(Symbol, String)>, +} + #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum Piece { Lit(Symbol), @@ -130,8 +263,17 @@ pub struct OnUnimplementedCondition { pub span: Span, pub pred: Predicate, } - impl OnUnimplementedCondition { + pub fn matches_predicate(self: &OnUnimplementedCondition, options: &ConditionOptions) -> bool { + self.pred.eval(&mut |p| match p { + FlagOrNv::Flag(b) => options.has_flag(*b), + FlagOrNv::NameValue(NameValue { name, value }) => { + let value = value.format(&options.generic_args); + options.contains(*name, value) + } + }) + } + pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { self.pred.visit_params(self.span, visit); } @@ -239,6 +381,23 @@ pub struct FilterFormatString { } impl FilterFormatString { + fn format(&self, generic_args: &[(Symbol, String)]) -> String { + let mut ret = String::new(); + + for piece in &self.pieces { + match piece { + LitOrArg::Lit(s) => ret.push_str(s.as_str()), + LitOrArg::Arg(s) => match generic_args.iter().find(|(k, _)| k == s) { + Some((_, val)) => ret.push_str(val), + None => { + let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); + } + }, + } + } + + ret + } pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { for piece in &self.pieces { if let LitOrArg::Arg(arg) = piece { diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 60725b6dc4e80..cc2bb285e77cf 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -45,7 +45,6 @@ use super::{ }; use crate::error_reporting::TypeErrCtxt; use crate::error_reporting::infer::TyCategory; -use crate::error_reporting::traits::on_unimplemented::evaluate_directive; use crate::error_reporting::traits::report_dyn_incompatibility; use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch, CoroClosureNotFn}; use crate::infer::{self, InferCtxt, InferCtxtExt as _}; @@ -914,9 +913,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { ); if let Some(command) = find_attr!(self.tcx.get_all_attrs(impl_did), AttributeKind::OnConst {directive, ..} => directive.as_deref()).flatten(){ - let note = evaluate_directive( - &command, - predicate.skip_binder().trait_ref, + let note = command.evaluate_directive( + predicate.skip_binder().trait_ref, &condition_options, &format_args, ); diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs index c239f0b56c83f..bda0c4fa2c6f6 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/mod.rs @@ -2,8 +2,6 @@ pub mod ambiguity; pub mod call_kind; pub mod fulfillment_errors; pub mod on_unimplemented; -pub mod on_unimplemented_condition; -pub mod on_unimplemented_format; mod overflow; pub mod suggestions; diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 955c9e04a1297..0676389a40294 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -2,19 +2,16 @@ use std::path::PathBuf; use rustc_hir as hir; use rustc_hir::attrs::AttributeKind; -use rustc_hir::attrs::diagnostic::{ConditionOptions, Directive, OnUnimplementedNote}; +use rustc_hir::attrs::diagnostic::{ConditionOptions, FormatArgs, OnUnimplementedNote}; use rustc_hir::def_id::LocalDefId; use rustc_hir::find_attr; pub use rustc_hir::lints::FormatWarning; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind}; use rustc_span::Symbol; -use tracing::{debug, info}; use super::{ObligationCauseCode, PredicateObligation}; use crate::error_reporting::TypeErrCtxt; -use crate::error_reporting::traits::on_unimplemented_condition::matches_predicate; -use crate::error_reporting::traits::on_unimplemented_format::FormatArgs; impl<'tcx> TypeErrCtxt<'_, 'tcx> { /// Used to set on_unimplemented's `ItemContext` @@ -48,8 +45,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let (condition_options, format_args) = self.on_unimplemented_components(trait_pred, obligation, long_ty_path); if let Some(command) = find_attr!(self.tcx.get_all_attrs( trait_pred.def_id()), AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { - evaluate_directive( - &command, + command.evaluate_directive( trait_pred.skip_binder().trait_ref, &condition_options, &format_args, @@ -64,7 +60,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { trait_pred: ty::PolyTraitPredicate<'tcx>, obligation: &PredicateObligation<'tcx>, long_ty_path: &mut Option, - ) -> (ConditionOptions, FormatArgs<'tcx>) { + ) -> (ConditionOptions, FormatArgs) { let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); @@ -213,7 +209,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { })); let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id); - let trait_sugared = trait_pred.trait_ref.print_trait_sugared(); + let trait_sugared = trait_pred.trait_ref.print_trait_sugared().to_string(); let condition_options = ConditionOptions { self_types, @@ -255,56 +251,3 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { (condition_options, format_args) } } - -pub(crate) fn evaluate_directive<'tcx>( - slf: &Directive, - trait_ref: ty::TraitRef<'tcx>, - condition_options: &ConditionOptions, - args: &FormatArgs<'tcx>, -) -> OnUnimplementedNote { - let mut message = None; - let mut label = None; - let mut notes = Vec::new(); - let mut parent_label = None; - let mut append_const_msg = None; - info!( - "evaluate_directive({:?}, trait_ref={:?}, options={:?}, args ={:?})", - slf, trait_ref, condition_options, args - ); - - for command in slf.subcommands.iter().chain(Some(slf)).rev() { - debug!(?command); - if let Some(ref condition) = command.condition - && !matches_predicate(condition, condition_options) - { - debug!("evaluate_directive: skipping {:?} due to condition", command); - continue; - } - debug!("evaluate_directive: {:?} succeeded", command); - if let Some(ref message_) = command.message { - message = Some(message_.clone()); - } - - if let Some(ref label_) = command.label { - label = Some(label_.clone()); - } - - notes.extend(command.notes.clone()); - - if let Some(ref parent_label_) = command.parent_label { - parent_label = Some(parent_label_.clone()); - } - - append_const_msg = command.append_const_msg; - } - - use crate::error_reporting::traits::on_unimplemented_format::format; - - OnUnimplementedNote { - label: label.map(|l| format(&l.1, args)), - message: message.map(|m| format(&m.1, args)), - notes: notes.into_iter().map(|n| format(&n, args)).collect(), - parent_label: parent_label.map(|e_s| format(&e_s, args)), - append_const_msg, - } -} diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs deleted file mode 100644 index c92b8dbc0fea2..0000000000000 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs +++ /dev/null @@ -1,30 +0,0 @@ -use rustc_hir::attrs::diagnostic::*; -use rustc_span::Symbol; - -pub fn matches_predicate(slf: &OnUnimplementedCondition, options: &ConditionOptions) -> bool { - slf.pred.eval(&mut |p| match p { - FlagOrNv::Flag(b) => options.has_flag(*b), - FlagOrNv::NameValue(NameValue { name, value }) => { - let value = format_filter(value, &options.generic_args); - options.contains(*name, value) - } - }) -} - -fn format_filter(slf: &FilterFormatString, generic_args: &[(Symbol, String)]) -> String { - let mut ret = String::new(); - - for piece in &slf.pieces { - match piece { - LitOrArg::Lit(s) => ret.push_str(s.as_str()), - LitOrArg::Arg(s) => match generic_args.iter().find(|(k, _)| k == s) { - Some((_, val)) => ret.push_str(val), - None => { - let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}")); - } - }, - } - } - - ret -} diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs deleted file mode 100644 index a68b17dc2f4cc..0000000000000 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::fmt; - -use rustc_hir::attrs::diagnostic::*; -use rustc_middle::ty::print::TraitRefPrintSugared; -use rustc_span::{Symbol, kw}; - -/// Arguments to fill a [FormatString] with. -/// -/// For example, given a -/// ```rust,ignore (just an example) -/// -/// #[rustc_on_unimplemented( -/// on(all(from_desugaring = "QuestionMark"), -/// message = "the `?` operator can only be used in {ItemContext} \ -/// that returns `Result` or `Option` \ -/// (or another type that implements `{FromResidual}`)", -/// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", -/// parent_label = "this function should return `Result` or `Option` to accept `?`" -/// ), -/// )] -/// pub trait FromResidual::Residual> { -/// ... -/// } -/// -/// async fn an_async_function() -> u32 { -/// let x: Option = None; -/// x?; //~ ERROR the `?` operator -/// 22 -/// } -/// ``` -/// it will look like this: -/// -/// ```rust,ignore (just an example) -/// FormatArgs { -/// this: "FromResidual", -/// trait_sugared: "FromResidual>", -/// item_context: "an async function", -/// generic_args: [("Self", "u32"), ("R", "Option")], -/// } -/// ``` -#[derive(Debug)] -pub struct FormatArgs<'tcx> { - pub this: String, - pub trait_sugared: TraitRefPrintSugared<'tcx>, - pub item_context: &'static str, - pub generic_args: Vec<(Symbol, String)>, -} - -pub fn format(slf: &FormatString, args: &FormatArgs<'_>) -> String { - let mut ret = String::new(); - for piece in &slf.pieces { - match piece { - Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(s.as_str()), - - // `A` if we have `trait Trait {}` and `note = "i'm the actual type of {A}"` - Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { - match args.generic_args.iter().find(|(p, _)| p == generic_param) { - Some((_, val)) => ret.push_str(val.as_str()), - - None => { - // Apparently this was not actually a generic parameter, so lets write - // what the user wrote. - let _ = fmt::write(&mut ret, format_args!("{{{generic_param}}}")); - } - } - } - // `{Self}` - Piece::Arg(FormatArg::SelfUpper) => { - let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { - Some((_, val)) => val.to_string(), - None => "Self".to_string(), - }; - ret.push_str(&slf); - } - - // It's only `rustc_onunimplemented` from here - Piece::Arg(FormatArg::This) => ret.push_str(&args.this), - Piece::Arg(FormatArg::Trait) => { - let _ = fmt::write(&mut ret, format_args!("{}", &args.trait_sugared)); - } - Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), - } - } - ret -} From 2a55caa8daf2a025403f2580b555681eee3eff5f Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 01:15:38 +0100 Subject: [PATCH 18/32] Warn on duplicate parent label --- compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 802d0148994b1..3e5198c4eb526 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -225,7 +225,7 @@ fn parse_directive_items<'p, S: Stage>( if parent_label.is_none() { parent_label = Some(parse_format(value)); } else { - // warn + duplicate!(name, span) } } (Mode::RustcOnUnimplemented, sym::on) => { From b44ec0e775b33209e207590dd91e1d09a023c961 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 01:29:52 +0100 Subject: [PATCH 19/32] Still finalize a on_const attribute if it is malformed --- .../src/attributes/diagnostic/on_const.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index 1b694b4611b50..4069554d81ebb 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -8,6 +8,7 @@ use crate::attributes::template; #[derive(Default)] pub(crate) struct OnConstParser { + span: Option, directive: Option<(Span, Directive)>, } @@ -17,7 +18,9 @@ impl AttributeParser for OnConstParser { if !cx.features().diagnostic_on_const() { return; } + let span = cx.attr_span; + this.span = Some(span); let items = match args { ArgParser::List(items) if items.len() != 0 => items, @@ -49,9 +52,10 @@ impl AttributeParser for OnConstParser { const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { - self.directive.map(|(span, directive)| AttributeKind::OnConst { - span, - directive: Some(Box::new(directive)), - }) + if let Some(span) = self.span { + Some(AttributeKind::OnConst { span, directive: self.directive.map(|d| Box::new(d.1)) }) + } else { + None + } } } From 5ba5bce29323b06727dbe5a07d32315995a769f2 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 01:45:52 +0100 Subject: [PATCH 20/32] Fix impossible doc link --- compiler/rustc_hir/src/attrs/diagnostic.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index d055770a8bdd0..783aa3b6d44e9 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -458,7 +458,7 @@ pub struct ConditionOptions { pub self_types: Vec, // The kind of compiler desugaring. pub from_desugaring: Option, - /// Match on a variant of [rustc_infer::traits::ObligationCauseCode]. + /// Match on a variant of rustc_infer's `ObligationCauseCode`. pub cause: Option, pub crate_local: bool, /// Is the obligation "directly" user-specified, rather than derived? From fb971c4dc8b9a464eec51485a239fa4032928059 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:12:25 +0100 Subject: [PATCH 21/32] Move do_not_recommend to diagnostic folder --- .../src/attributes/{ => diagnostic}/do_not_recommend.rs | 0 compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs | 1 + compiler/rustc_attr_parsing/src/attributes/mod.rs | 1 - compiler/rustc_attr_parsing/src/context.rs | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename compiler/rustc_attr_parsing/src/attributes/{ => diagnostic}/do_not_recommend.rs (100%) diff --git a/compiler/rustc_attr_parsing/src/attributes/do_not_recommend.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs similarity index 100% rename from compiler/rustc_attr_parsing/src/attributes/do_not_recommend.rs rename to compiler/rustc_attr_parsing/src/attributes/diagnostic/do_not_recommend.rs diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 3e5198c4eb526..193f761d22420 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -20,6 +20,7 @@ use thin_vec::{ThinVec, thin_vec}; use crate::context::{AcceptContext, Stage}; use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser}; +pub(crate) mod do_not_recommend; pub(crate) mod on_const; pub(crate) mod on_unimplemented; diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 9047ac8a86108..8ee453d7f4649 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -40,7 +40,6 @@ pub(crate) mod crate_level; pub(crate) mod debugger; pub(crate) mod deprecation; pub(crate) mod diagnostic; -pub(crate) mod do_not_recommend; pub(crate) mod doc; pub(crate) mod dummy; pub(crate) mod inline; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 2c9f5fc24895f..e615d53bc7f9a 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -26,9 +26,9 @@ use crate::attributes::confusables::*; use crate::attributes::crate_level::*; use crate::attributes::debugger::*; use crate::attributes::deprecation::*; +use crate::attributes::diagnostic::do_not_recommend::*; use crate::attributes::diagnostic::on_const::*; use crate::attributes::diagnostic::on_unimplemented::*; -use crate::attributes::do_not_recommend::*; use crate::attributes::doc::*; use crate::attributes::dummy::*; use crate::attributes::inline::*; From f0f46d2595df3c886ef32fa02a1f7f7997681a7a Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 13:12:41 +0100 Subject: [PATCH 22/32] Split derive attribute --- compiler/rustc_hir/src/attrs/diagnostic.rs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_hir/src/attrs/diagnostic.rs index 783aa3b6d44e9..7c66b3f844691 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_hir/src/attrs/diagnostic.rs @@ -116,18 +116,8 @@ pub struct OnUnimplementedNote { } /// Append a message for `[const] Trait` errors. -#[derive( - Clone, - Copy, - PartialEq, - Eq, - Debug, - Default, - HashStable_Generic, - Encodable, - Decodable, - PrintAttribute -)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +#[derive(HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum AppendConstMessage { #[default] Default, From 03713d0dd3d3dd6c5c6ca1ba6a2232794dd97d73 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 15:25:31 +0100 Subject: [PATCH 23/32] Remove redundant import --- .../rustc_attr_parsing/src/attributes/diagnostic/on_const.rs | 1 - .../src/attributes/diagnostic/on_unimplemented.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index 4069554d81ebb..13378953b849b 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -4,7 +4,6 @@ use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::attributes::template; #[derive(Default)] pub(crate) struct OnConstParser { diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index ae3da2525ba2d..4b3443801f851 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -4,7 +4,6 @@ use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES; use crate::attributes::diagnostic::*; use crate::attributes::prelude::*; -use crate::attributes::template; #[derive(Default)] pub(crate) struct OnUnimplementedParser { From 0e10ce27aecc2a9fa785e5fd2212d5166c0ba5f8 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:09:34 +0100 Subject: [PATCH 24/32] Explain early return in parser --- .../src/attributes/diagnostic/on_unimplemented.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index 4b3443801f851..b2233ce04b387 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -20,6 +20,10 @@ impl OnUnimplementedParser { ) { let span = cx.attr_span; self.span = Some(span); + + // If target is not a trait, returning early will make `finalize` emit a + // `AttributeKind::OnUnimplemented {span, directive: None }`, to prevent it being + // accidentally used on non-trait items like trait aliases. if !matches!(cx.target, Target::Trait) { // Lint later emitted in check_attr return; From f3662f4dd3980f22795508bd83bd0d33116d0355 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:31:21 +0100 Subject: [PATCH 25/32] use raw string literal --- compiler/rustc_passes/src/errors.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 18414a596a6bf..4bb8b06f129cc 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1456,6 +1456,6 @@ pub(crate) struct UnknownFormatParameterForOnUnimplementedAttr { pub argument_name: Symbol, pub trait_name: Ident, // `false` if we're in rustc_on_unimplemented, since its syntax is a lot more complex. - #[help("expect either a generic argument name or {\"`{Self}`\"} as format argument")] + #[help(r#"expect either a generic argument name or {"`{Self}`"} as format argument"#)] pub help: bool, } From a9c068cb2c29404d2f5ebc73ed88452e4bffb3fa Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Thu, 19 Feb 2026 22:01:02 +0100 Subject: [PATCH 26/32] address feedback --- .../src/attributes/diagnostic/mod.rs | 6 +++--- .../src/attributes/diagnostic/on_const.rs | 11 ++++++++--- .../src/attributes/diagnostic/on_unimplemented.rs | 11 ++++++++--- compiler/rustc_attr_parsing/src/lib.rs | 1 - 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 193f761d22420..8f114b3284485 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -118,9 +118,9 @@ fn parse_directive_items<'p, S: Stage>( }} macro or_malformed($($code:tt)*) {{ - let Some(ret) = (try { - $($code)* - }) else { + let Some(ret) = (||{ + Some($($code)*) + })() else { malformed!() }; diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs index 13378953b849b..def4069f6b477 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_const.rs @@ -12,8 +12,10 @@ pub(crate) struct OnConstParser { } impl AttributeParser for OnConstParser { - const ATTRIBUTES: AcceptMapping = - &[(&[sym::diagnostic, sym::on_const], template!(Word), |this, cx, args| { + const ATTRIBUTES: AcceptMapping = &[( + &[sym::diagnostic, sym::on_const], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { if !cx.features().diagnostic_on_const() { return; } @@ -47,7 +49,10 @@ impl AttributeParser for OnConstParser { return; }; merge_directives(cx, &mut this.directive, (span, directive)); - })]; + }, + )]; + + //FIXME Still checked in `check_attr.rs` const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs index b2233ce04b387..12028059b7d40 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unimplemented.rs @@ -57,9 +57,13 @@ impl OnUnimplementedParser { impl AttributeParser for OnUnimplementedParser { const ATTRIBUTES: AcceptMapping = &[ - (&[sym::diagnostic, sym::on_unimplemented], template!(Word), |this, cx, args| { - this.parse(cx, args, Mode::DiagnosticOnUnimplemented); - }), + ( + &[sym::diagnostic, sym::on_unimplemented], + template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + |this, cx, args| { + this.parse(cx, args, Mode::DiagnosticOnUnimplemented); + }, + ), ( &[sym::rustc_on_unimplemented], template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), @@ -68,6 +72,7 @@ impl AttributeParser for OnUnimplementedParser { }, ), ]; + //FIXME attribute is not parsed for non-traits but diagnostics are issued in `check_attr.rs` const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS); fn finalize(self, _cx: &FinalizeContext<'_, '_, S>) -> Option { diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index b70eb93d443a7..76afcb2a260ad 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -80,7 +80,6 @@ #![cfg_attr(bootstrap, feature(if_let_guard))] #![feature(decl_macro)] #![feature(iter_intersperse)] -#![feature(try_blocks)] #![recursion_limit = "256"] // tidy-alphabetical-end From 8ac769f9d0675d7ca28ad01396ab204d8e3a96d7 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Mon, 23 Feb 2026 14:03:16 +1100 Subject: [PATCH 27/32] 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 98ab94b6a57aed3db72b49b1905a803960428e20 Mon Sep 17 00:00:00 2001 From: mejrs <59372212+mejrs@users.noreply.github.com> Date: Tue, 24 Feb 2026 00:08:57 +0100 Subject: [PATCH 28/32] Use proper find_attr syntax --- compiler/rustc_hir_typeck/src/method/suggest.rs | 1 - .../src/error_reporting/traits/fulfillment_errors.rs | 3 +-- .../src/error_reporting/traits/on_unimplemented.rs | 3 +-- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 1f496bc5ce270..50bdedf973854 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -16,7 +16,6 @@ use rustc_errors::codes::*; use rustc_errors::{ Applicability, Diag, MultiSpan, StashKey, listify, pluralize, struct_span_code_err, }; -use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::OnUnimplementedNote; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index cc2bb285e77cf..f4a9b6635c91b 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -14,7 +14,6 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, Level, MultiSpan, StashKey, StringPart, Suggestions, msg, pluralize, struct_span_code_err, }; -use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{AppendConstMessage, OnUnimplementedNote}; use rustc_hir::def_id::{DefId, LOCAL_CRATE, LocalDefId}; use rustc_hir::intravisit::Visitor; @@ -912,7 +911,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { diag.long_ty_path(), ); - if let Some(command) = find_attr!(self.tcx.get_all_attrs(impl_did), AttributeKind::OnConst {directive, ..} => directive.as_deref()).flatten(){ + if let Some(command) = find_attr!(self.tcx, impl_did, OnConst {directive, ..} => directive.as_deref()).flatten(){ let note = command.evaluate_directive( predicate.skip_binder().trait_ref, &condition_options, diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs index 0676389a40294..d08e0fa3521bb 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs @@ -1,7 +1,6 @@ use std::path::PathBuf; use rustc_hir as hir; -use rustc_hir::attrs::AttributeKind; use rustc_hir::attrs::diagnostic::{ConditionOptions, FormatArgs, OnUnimplementedNote}; use rustc_hir::def_id::LocalDefId; use rustc_hir::find_attr; @@ -44,7 +43,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { } let (condition_options, format_args) = self.on_unimplemented_components(trait_pred, obligation, long_ty_path); - if let Some(command) = find_attr!(self.tcx.get_all_attrs( trait_pred.def_id()), AttributeKind::OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { + if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() { command.evaluate_directive( trait_pred.skip_binder().trait_ref, &condition_options, From bf0f5115c3530e41fa73e5ef47d4bd7d37e1d23a Mon Sep 17 00:00:00 2001 From: Zalathar Date: Tue, 24 Feb 2026 13:00:15 +1100 Subject: [PATCH 29/32] 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 30/32] 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 31/32] 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 32/32] 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`.